diff --git a/frontend/app/aipanel/aipanelheader.tsx b/frontend/app/aipanel/aipanelheader.tsx
index da54f6c9e9..a2491e409d 100644
--- a/frontend/app/aipanel/aipanelheader.tsx
+++ b/frontend/app/aipanel/aipanelheader.tsx
@@ -6,9 +6,55 @@ import { useAtomValue } from "jotai";
import { memo } from "react";
import { WaveAIModel } from "./waveai-model";
+const ToggleSwitch = memo(
+ ({
+ label,
+ shortLabel,
+ enabled,
+ onToggle,
+ title,
+ }: {
+ label: string;
+ shortLabel: string;
+ enabled: boolean;
+ onToggle: () => void;
+ title: string;
+ }) => (
+
+ )
+);
+ToggleSwitch.displayName = "ToggleSwitch";
+
export const AIPanelHeader = memo(() => {
const model = WaveAIModel.getInstance();
const widgetAccess = useAtomValue(model.widgetAccessAtom);
+ const mcpContext = useAtomValue(model.mcpContextAtom);
const inBuilder = model.inBuilder;
const handleKebabClick = (e: React.MouseEvent) => {
@@ -29,42 +75,35 @@ export const AIPanelHeader = memo(() => {
Wave AI
-
+
{!inBuilder && (
-
- Context
- Widget Context
- {
+ <>
+ {
model.setWidgetAccess(!widgetAccess);
- setTimeout(() => {
- model.focusInput();
- }, 0);
+ setTimeout(() => model.focusInput(), 0);
}}
- className={`relative inline-flex h-6 w-14 items-center rounded-full transition-colors cursor-pointer ${
- widgetAccess ? "bg-accent-600" : "bg-zinc-600"
- }`}
title={`Widget Access ${widgetAccess ? "ON" : "OFF"}`}
- >
-
-
- {widgetAccess ? "ON" : "OFF"}
-
-
-
+ />
+
{
+ model.setMCPContext(!mcpContext);
+ setTimeout(() => model.focusInput(), 0);
+ }}
+ title={`MCP Context ${mcpContext ? "ON" : "OFF"}`}
+ />
+ >
)}
diff --git a/frontend/app/aipanel/aitooluse.tsx b/frontend/app/aipanel/aitooluse.tsx
index 7868c188e9..48f9888aa0 100644
--- a/frontend/app/aipanel/aitooluse.tsx
+++ b/frontend/app/aipanel/aitooluse.tsx
@@ -77,6 +77,32 @@ const ToolDesc = memo(({ text, className }: ToolDescProps) => {
ToolDesc.displayName = "ToolDesc";
+// Extract directory path from a tool description like: reading "/path/to/file" (...)
+function extractDirFromToolDesc(toolDesc: string): string | null {
+ const match = toolDesc?.match(/(?:reading|reading directory)\s+"([^"]+)"/);
+ if (!match) return null;
+ const filePath = match[1];
+ // For "reading directory" — the path itself is the directory
+ if (toolDesc.startsWith("reading directory")) {
+ return filePath;
+ }
+ // For "reading" (file) — get parent directory (handle both / and \ separators)
+ const lastSlash = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
+ if (lastSlash < 0) return filePath;
+ if (lastSlash === 0) return filePath.substring(0, 1);
+ return filePath.substring(0, lastSlash);
+}
+
+// Extract all unique directories from a set of tool use parts
+function extractDirsFromParts(parts: Array): string[] {
+ const dirs = new Set();
+ for (const part of parts) {
+ const dir = extractDirFromToolDesc(part.data.tooldesc);
+ if (dir) dirs.add(dir);
+ }
+ return Array.from(dirs);
+}
+
function getEffectiveApprovalStatus(baseApproval: string, isStreaming: boolean): string {
return !isStreaming && baseApproval === "needs-approval" ? "timeout" : baseApproval;
}
@@ -85,9 +111,11 @@ interface AIToolApprovalButtonsProps {
count: number;
onApprove: () => void;
onDeny: () => void;
+ onAllowSession?: () => void;
+ showSessionButton?: boolean;
}
-const AIToolApprovalButtons = memo(({ count, onApprove, onDeny }: AIToolApprovalButtonsProps) => {
+const AIToolApprovalButtons = memo(({ count, onApprove, onDeny, onAllowSession, showSessionButton }: AIToolApprovalButtonsProps) => {
const approveText = count > 1 ? `Approve All (${count})` : "Approve";
const denyText = count > 1 ? "Deny All" : "Deny";
@@ -99,6 +127,15 @@ const AIToolApprovalButtons = memo(({ count, onApprove, onDeny }: AIToolApproval
>
{approveText}
+ {showSessionButton && onAllowSession && (
+
+ Allow reading in this session
+
+ )}
{
});
};
+ const handleAllowSession = () => {
+ const dirs = extractDirsFromParts(parts);
+ const model = WaveAIModel.getInstance();
+ for (const dir of dirs) {
+ model.sessionReadApprove(dir);
+ }
+ handleApprove();
+ };
+
+ const isReadOp = parts.some(
+ (p) => p.data.toolname === "read_text_file" || p.data.toolname === "read_dir"
+ );
+
return (
@@ -175,7 +225,13 @@ const AIToolUseBatch = memo(({ parts, isStreaming }: AIToolUseBatchProps) => {
))}
{effectiveApproval === "needs-approval" && (
-
+
)}
@@ -215,6 +271,8 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
};
}, []);
+ const isReadTool = toolData.toolname === "read_text_file" || toolData.toolname === "read_dir";
+
const handleApprove = () => {
setUserApprovalOverride("user-approved");
WaveAIModel.getInstance().toolUseSendApproval(toolData.toolcallid, "user-approved");
@@ -225,6 +283,14 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
WaveAIModel.getInstance().toolUseSendApproval(toolData.toolcallid, "user-denied");
};
+ const handleAllowSession = () => {
+ const dir = extractDirFromToolDesc(toolData.tooldesc);
+ if (dir) {
+ WaveAIModel.getInstance().sessionReadApprove(dir);
+ }
+ handleApprove();
+ };
+
const handleMouseEnter = () => {
if (!toolData.blockid) return;
@@ -309,7 +375,13 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
)}
{effectiveApproval === "needs-approval" && (
)}
{showRestoreModal &&
}
diff --git a/frontend/app/aipanel/apikeyinput.tsx b/frontend/app/aipanel/apikeyinput.tsx
new file mode 100644
index 0000000000..8b14053895
--- /dev/null
+++ b/frontend/app/aipanel/apikeyinput.tsx
@@ -0,0 +1,87 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { globalStore } from "@/app/store/jotaiStore";
+import { RpcApi } from "@/app/store/wshclientapi";
+import { TabRpcClient } from "@/app/store/wshrpcutil";
+import { useAtomValue } from "jotai";
+import { memo, useCallback, useState } from "react";
+import { WaveAIModel } from "./waveai-model";
+
+export const ApiKeyInput = memo(() => {
+ const model = WaveAIModel.getInstance();
+ const inputData = useAtomValue(model.showApiKeyInput);
+ const [apiKey, setApiKey] = useState("");
+ const [saving, setSaving] = useState(false);
+
+ const handleSave = useCallback(async () => {
+ console.log("[apikeyinput] handleSave called", { apiKey: apiKey ? "***" : "empty", inputData });
+ if (!apiKey.trim() || !inputData) {
+ console.log("[apikeyinput] early return - missing data");
+ return;
+ }
+ setSaving(true);
+ try {
+ console.log("[apikeyinput] saving secret:", inputData.secretName);
+ await RpcApi.SetSecretsCommand(TabRpcClient, {
+ [inputData.secretName]: apiKey.trim(),
+ });
+ console.log("[apikeyinput] secret saved, switching to mode:", inputData.presetKey);
+ model.setAIMode(inputData.presetKey);
+ globalStore.set(model.showApiKeyInput, null);
+ setApiKey("");
+ } catch (e) {
+ console.error("[apikeyinput] Failed to save API key:", e);
+ }
+ setSaving(false);
+ }, [apiKey, inputData, model]);
+
+ const handleCancel = useCallback(() => {
+ globalStore.set(model.showApiKeyInput, null);
+ setApiKey("");
+ }, [model]);
+
+ if (!inputData) return null;
+
+ return (
+
+
+
+ {inputData.secretLabel}
+
+
+ setApiKey(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") handleSave();
+ if (e.key === "Escape") handleCancel();
+ }}
+ placeholder="sk-... or API key"
+ className="flex-1 bg-gray-900 text-gray-200 text-xs font-mono px-2 py-1.5 rounded border border-gray-600 focus:border-accent-500 focus:outline-none"
+ spellCheck={false}
+ autoFocus
+ />
+
+ {saving ? "Saving..." : "Save & Activate"}
+
+
+
+
+
+
+ Key is stored securely in Wave's secret store, not in config files.
+
+
+ );
+});
+
+ApiKeyInput.displayName = "ApiKeyInput";
diff --git a/frontend/app/aipanel/mcpdetect.tsx b/frontend/app/aipanel/mcpdetect.tsx
new file mode 100644
index 0000000000..8cc76f07d7
--- /dev/null
+++ b/frontend/app/aipanel/mcpdetect.tsx
@@ -0,0 +1,199 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { globalStore } from "@/app/store/jotaiStore";
+import { WOS } from "@/store/global";
+import { getWebServerEndpoint } from "@/util/endpoints";
+import { fetch } from "@/util/fetchutil";
+import { useAtomValue } from "jotai";
+import { memo, useCallback, useEffect, useState } from "react";
+import { WaveAIModel } from "./waveai-model";
+
+type MCPDetectInfo = {
+ found: boolean;
+ serverName?: string;
+ serverVersion?: string;
+ toolCount?: number;
+ cwd?: string;
+};
+
+async function detectMCPInCwd(cwd: string): Promise
{
+ try {
+ const resp = await fetch(getWebServerEndpoint() + `/wave/mcp/status?cwd=${encodeURIComponent(cwd)}`);
+ const data = await resp.json();
+ if (data.connected || data.tools) {
+ return {
+ found: true,
+ serverName: data.serverInfo?.name || data.serverName,
+ serverVersion: data.serverInfo?.version,
+ toolCount: data.tools?.length || 0,
+ cwd,
+ };
+ }
+ // Server config exists but couldn't connect — still show banner
+ if (!data.error?.includes("no .mcp.json")) {
+ return {
+ found: true,
+ serverName: data.serverName,
+ cwd,
+ };
+ }
+ return { found: false };
+ } catch {
+ return { found: false };
+ }
+}
+
+function getTerminalCwdFromTab(tabId: string): string | null {
+ const tabAtom = WOS.getWaveObjectAtom(`tab:${tabId}`);
+ const tab = globalStore.get(tabAtom);
+ if (!tab?.blockids) return null;
+
+ for (const blockId of tab.blockids) {
+ const blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`);
+ const block = globalStore.get(blockAtom);
+ if (!block?.meta) continue;
+ const viewType = block.meta["view"] as string;
+ if (viewType !== "term") continue;
+ const cwd = block.meta["cmd:cwd"] as string;
+ if (cwd) return cwd;
+ }
+ return null;
+}
+
+export const MCPDetectBanner = memo(({ tabId }: { tabId: string }) => {
+ const model = WaveAIModel.getInstance();
+ const mcpEnabled = useAtomValue(model.mcpContextAtom);
+ const [detectInfo, setDetectInfo] = useState(null);
+ const [dismissed, setDismissed] = useState(false);
+
+ useEffect(() => {
+ if (mcpEnabled || dismissed) {
+ setDetectInfo(null);
+ return;
+ }
+
+ const cwd = getTerminalCwdFromTab(tabId);
+ if (!cwd) {
+ setDetectInfo(null);
+ return;
+ }
+
+ let cancelled = false;
+ detectMCPInCwd(cwd).then((info) => {
+ if (!cancelled) {
+ setDetectInfo(info);
+ }
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [tabId, mcpEnabled, dismissed]);
+
+ const handleConnect = useCallback(() => {
+ model.setMCPContext(true);
+ setDetectInfo(null);
+ }, [model]);
+
+ const handleDismiss = useCallback(() => {
+ setDismissed(true);
+ setDetectInfo(null);
+ }, []);
+
+ if (!detectInfo?.found || mcpEnabled) {
+ return null;
+ }
+
+ const serverLabel = detectInfo.serverName
+ ? `${detectInfo.serverName}${detectInfo.serverVersion ? ` v${detectInfo.serverVersion}` : ""}`
+ : ".mcp.json";
+
+ return (
+
+
+
+
+ Found {serverLabel}
+
+ {detectInfo.toolCount ? (
+ ({detectInfo.toolCount} tools)
+ ) : null}
+
+
+ Connect
+
+
+
+
+
+ );
+});
+
+MCPDetectBanner.displayName = "MCPDetectBanner";
+
+export const MCPConnectInput = memo(() => {
+ const model = WaveAIModel.getInstance();
+ const showInput = useAtomValue(model.showMCPConnectInput);
+ const [inputCwd, setInputCwd] = useState("");
+
+ const handleConnect = useCallback(() => {
+ if (!inputCwd.trim()) return;
+ model.setMCPCwd(inputCwd.trim());
+ model.setMCPContext(true);
+ globalStore.set(model.showMCPConnectInput, false);
+ setInputCwd("");
+ }, [inputCwd, model]);
+
+ const handleCancel = useCallback(() => {
+ globalStore.set(model.showMCPConnectInput, false);
+ setInputCwd("");
+ }, [model]);
+
+ if (!showInput) return null;
+
+ return (
+
+
+
+ Connect to MCP server
+
+
+ setInputCwd(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") handleConnect();
+ if (e.key === "Escape") handleCancel();
+ }}
+ placeholder="/path/to/project (with .mcp.json)"
+ className="flex-1 bg-gray-900 text-gray-200 text-xs font-mono px-2 py-1.5 rounded border border-gray-600 focus:border-accent-500 focus:outline-none"
+ spellCheck={false}
+ autoFocus
+ />
+
+ Connect
+
+
+
+
+
+
+ );
+});
+
+MCPConnectInput.displayName = "MCPConnectInput";
diff --git a/frontend/app/aipanel/planprogress.tsx b/frontend/app/aipanel/planprogress.tsx
new file mode 100644
index 0000000000..5feafeb500
--- /dev/null
+++ b/frontend/app/aipanel/planprogress.tsx
@@ -0,0 +1,165 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getWebServerEndpoint } from "@/util/endpoints";
+import { fetch } from "@/util/fetchutil";
+import { memo, useCallback, useEffect, useState } from "react";
+
+type PlanStep = {
+ id: number;
+ label: string;
+ status: string;
+ result?: string;
+ error?: string;
+ doneAt?: string;
+};
+
+type Plan = {
+ tabId: string;
+ name: string;
+ description?: string;
+ createdAt: string;
+ updatedAt: string;
+ steps: PlanStep[];
+};
+
+async function fetchPlan(tabId: string): Promise {
+ try {
+ const resp = await fetch(getWebServerEndpoint() + `/wave/plan/status?tabid=${encodeURIComponent(tabId)}`);
+ const data = await resp.json();
+ return data?.plan ?? null;
+ } catch {
+ return null;
+ }
+}
+
+const statusIcon: Record = {
+ pending: { icon: "fa-circle", color: "text-gray-500" },
+ running: { icon: "fa-spinner fa-spin", color: "text-accent-400" },
+ done: { icon: "fa-check-circle", color: "text-green-400" },
+ failed: { icon: "fa-times-circle", color: "text-red-400" },
+ skipped: { icon: "fa-minus-circle", color: "text-gray-400" },
+};
+
+const PlanStepItem = memo(({ step }: { step: PlanStep }) => {
+ const [expanded, setExpanded] = useState(false);
+ const hasDetails = step.result || step.error;
+ const { icon, color } = statusIcon[step.status] ?? statusIcon.pending;
+
+ return (
+
+
hasDetails && setExpanded(!expanded)}
+ >
+
+
+ {step.label}
+
+ {hasDetails && (
+
+ )}
+
+ {expanded && hasDetails && (
+
+ {step.error && (
+
+ {step.error}
+
+ )}
+ {step.result && (
+
+ {step.result}
+
+ )}
+
+ )}
+
+ );
+});
+PlanStepItem.displayName = "PlanStepItem";
+
+export const PlanProgressPanel = memo(({ tabId }: { tabId: string }) => {
+ const [plan, setPlan] = useState(null);
+ const [minimized, setMinimized] = useState(false);
+
+ const refresh = useCallback(() => {
+ fetchPlan(tabId).then(setPlan);
+ }, [tabId]);
+
+ const dismissPlan = useCallback(async () => {
+ try {
+ await fetch(getWebServerEndpoint() + `/wave/plan/delete?tabid=${encodeURIComponent(tabId)}`);
+ } catch {}
+ setPlan(null);
+ }, [tabId]);
+
+ useEffect(() => {
+ refresh();
+ const interval = setInterval(refresh, 3000);
+ return () => clearInterval(interval);
+ }, [refresh]);
+
+ if (!plan || plan.steps.length === 0) return null;
+
+ const doneCount = plan.steps.filter(
+ (s) => s.status === "done" || s.status === "failed" || s.status === "skipped"
+ ).length;
+ const totalCount = plan.steps.length;
+ const isComplete = doneCount === totalCount;
+ const progressPct = Math.round((doneCount / totalCount) * 100);
+
+ return (
+
+ {/* Header */}
+
setMinimized(!minimized)}
+ >
+
+ {plan.name}
+
+ {doneCount}/{totalCount}
+
+ {
+ e.stopPropagation();
+ dismissPlan();
+ }}
+ className="text-gray-500 hover:text-white transition-colors ml-1"
+ title="Close plan"
+ >
+
+
+
+
+
+ {/* Progress bar */}
+ {!minimized && (
+ <>
+
+
+ {/* Steps */}
+
+ {plan.steps.map((step) => (
+
+ ))}
+
+
+ {plan.description && (
+
+ {plan.description}
+
+ )}
+ >
+ )}
+
+ );
+});
+
+PlanProgressPanel.displayName = "PlanProgressPanel";
diff --git a/frontend/app/aipanel/sessionhistory.tsx b/frontend/app/aipanel/sessionhistory.tsx
new file mode 100644
index 0000000000..32b89fb5ce
--- /dev/null
+++ b/frontend/app/aipanel/sessionhistory.tsx
@@ -0,0 +1,103 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { getWebServerEndpoint } from "@/util/endpoints";
+import { fetch } from "@/util/fetchutil";
+import { memo, useCallback, useEffect, useState } from "react";
+
+type SessionEntry = {
+ role: string;
+ text: string;
+ tool?: string;
+};
+
+type SessionLog = {
+ tabId: string;
+ timestamp: string;
+ model?: string;
+ entries?: SessionEntry[];
+};
+
+async function fetchSessionHistory(tabId: string): Promise {
+ try {
+ const resp = await fetch(getWebServerEndpoint() + `/wave/session-history?tabid=${encodeURIComponent(tabId)}`);
+ const data = await resp.json();
+ if (data?.entries && data.entries.length > 0) {
+ return data as SessionLog;
+ }
+ return null;
+ } catch {
+ return null;
+ }
+}
+
+export const SessionHistoryBanner = memo(({ tabId }: { tabId: string }) => {
+ const [session, setSession] = useState(null);
+ const [expanded, setExpanded] = useState(false);
+ const [dismissed, setDismissed] = useState(false);
+
+ useEffect(() => {
+ if (dismissed) return;
+ let cancelled = false;
+ fetchSessionHistory(tabId).then((data) => {
+ if (!cancelled) setSession(data);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [tabId, dismissed]);
+
+ if (!session || dismissed) return null;
+
+ const timestamp = new Date(session.timestamp).toLocaleString();
+ const entryCount = session.entries?.length ?? 0;
+
+ return (
+
+
setExpanded(!expanded)}
+ >
+
+
+
+ Previous session ({timestamp})
+
+ {entryCount} messages
+ {
+ e.stopPropagation();
+ setDismissed(true);
+ }}
+ className="text-gray-500 hover:text-white transition-colors ml-1"
+ title="Dismiss"
+ >
+
+
+
+ {expanded && session.entries && (
+
+ {session.entries.map((entry, idx) => (
+
+
+ {entry.role === "user" ? "You" : "AI"}
+
+
+ {entry.tool && (
+ [{entry.tool}]
+ )}
+ {entry.text}
+
+
+ ))}
+
+ )}
+
+ );
+});
+
+SessionHistoryBanner.displayName = "SessionHistoryBanner";
diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx
index 9af1d88508..70096c5d11 100644
--- a/frontend/app/aipanel/waveai-model.tsx
+++ b/frontend/app/aipanel/waveai-model.tsx
@@ -56,6 +56,10 @@ export class WaveAIModel {
isAIStreaming = jotai.atom(false);
widgetAccessAtom!: jotai.Atom;
+ mcpContextAtom!: jotai.Atom;
+ mcpCwdAtom!: jotai.Atom;
+ showMCPConnectInput: jotai.PrimitiveAtom = jotai.atom(false);
+ showApiKeyInput: jotai.PrimitiveAtom<{ presetKey: string; secretName: string; secretLabel: string } | null> = jotai.atom(null as any);
droppedFiles: jotai.PrimitiveAtom = jotai.atom([]);
chatId!: jotai.PrimitiveAtom;
currentAIMode!: jotai.PrimitiveAtom;
@@ -96,6 +100,21 @@ export class WaveAIModel {
return value ?? true;
});
+ this.mcpContextAtom = jotai.atom((get) => {
+ if (this.inBuilder) {
+ return false;
+ }
+ const mcpContextMetaAtom = getOrefMetaKeyAtom(this.orefContext, "waveai:mcpcontext");
+ const value = get(mcpContextMetaAtom);
+ return value ?? false;
+ });
+
+ this.mcpCwdAtom = jotai.atom((get) => {
+ const mcpCwdMetaAtom = getOrefMetaKeyAtom(this.orefContext, "waveai:mcpcwd");
+ const value = get(mcpCwdMetaAtom);
+ return (value as string) ?? "";
+ });
+
this.codeBlockMaxWidth = jotai.atom((get) => {
const width = get(this.containerWidth);
return width > 0 ? width - 35 : 0;
@@ -393,6 +412,20 @@ export class WaveAIModel {
});
}
+ setMCPContext(enabled: boolean) {
+ RpcApi.SetMetaCommand(TabRpcClient, {
+ oref: this.orefContext,
+ meta: { "waveai:mcpcontext": enabled },
+ });
+ }
+
+ setMCPCwd(cwd: string) {
+ RpcApi.SetMetaCommand(TabRpcClient, {
+ oref: this.orefContext,
+ meta: { "waveai:mcpcwd": cwd },
+ });
+ }
+
isValidMode(mode: string): boolean {
const telemetryEnabled = globalStore.get(getSettingsKeyAtom("telemetry:enabled")) ?? false;
if (mode.startsWith("waveai@") && !telemetryEnabled) {
@@ -612,6 +645,12 @@ export class WaveAIModel {
});
}
+ sessionReadApprove(path: string) {
+ RpcApi.WaveAISessionReadApproveCommand(TabRpcClient, {
+ path: path,
+ });
+ }
+
async openDiff(fileName: string, toolcallid: string) {
const chatId = this.getChatId();
diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx
index 126f208813..642e34cf21 100644
--- a/frontend/app/block/block.tsx
+++ b/frontend/app/block/block.tsx
@@ -34,6 +34,7 @@ import clsx from "clsx";
import { atom, useAtomValue } from "jotai";
import { memo, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { QuickTipsViewModel } from "../view/quicktipsview/quicktipsview";
+import { MCPClientViewModel } from "../view/mcpclient/mcpclient";
import { WaveConfigViewModel } from "../view/waveconfig/waveconfig-model";
import "./block.scss";
import { BlockEnv } from "./blockenv";
@@ -54,6 +55,7 @@ BlockRegistry.set("launcher", LauncherViewModel);
BlockRegistry.set("tsunami", TsunamiViewModel);
BlockRegistry.set("aifilediff", AiFileDiffViewModel);
BlockRegistry.set("waveconfig", WaveConfigViewModel);
+BlockRegistry.set("mcpclient", MCPClientViewModel);
function makeViewModel(
blockId: string,
diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx
index 01346183a0..4cc0c95b61 100644
--- a/frontend/app/block/blockutil.tsx
+++ b/frontend/app/block/blockutil.tsx
@@ -31,6 +31,9 @@ export function blockViewToIcon(view: string): string {
if (view == "tips") {
return "lightbulb";
}
+ if (view == "mcpclient") {
+ return "plug";
+ }
return "square";
}
@@ -56,6 +59,9 @@ export function blockViewToName(view: string): string {
if (view == "tips") {
return "Tips";
}
+ if (view == "mcpclient") {
+ return "MCP Client";
+ }
return view;
}
diff --git a/frontend/app/element/streamdown.tsx b/frontend/app/element/streamdown.tsx
index 2426f385e2..22266d355a 100644
--- a/frontend/app/element/streamdown.tsx
+++ b/frontend/app/element/streamdown.tsx
@@ -148,6 +148,7 @@ const CodeBlock = ({ children, onClickExecute, codeBlockMaxWidthAtom }: CodeBloc
};
const language = getLanguage(children);
+ const isShellLanguage = /^(bash|sh|shell|zsh|fish|ksh|csh|tcsh|powershell|pwsh|cmd|bat)$/i.test(language);
return (
{language}
- {onClickExecute && (
+ {onClickExecute && isShellLanguage && (
{
+ if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaisessionreadapprove", data, opts);
+ return client.wshRpcCall("waveaisessionreadapprove", data, opts);
+ }
+
// command "waveaitoolapprove" [call]
WaveAIToolApproveCommand(client: WshClient, data: CommandWaveAIToolApproveData, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaitoolapprove", data, opts);
diff --git a/frontend/app/view/aifilediff/aifilediff.tsx b/frontend/app/view/aifilediff/aifilediff.tsx
index 9d96d290c2..fc48ec22dc 100644
--- a/frontend/app/view/aifilediff/aifilediff.tsx
+++ b/frontend/app/view/aifilediff/aifilediff.tsx
@@ -96,8 +96,8 @@ function AiFileDiffView({ blockId, model }: ViewComponentProps = {
+ ts: "typescript", tsx: "typescriptreact",
+ js: "javascript", jsx: "javascriptreact",
+ php: "php", vue: "html",
+ py: "python", rb: "ruby",
+ go: "go", rs: "rust", java: "java",
+ css: "css", scss: "scss", less: "less",
+ html: "html", json: "json",
+ yaml: "yaml", yml: "yaml",
+ md: "markdown", sql: "sql",
+ sh: "shell", bash: "shell", zsh: "shell",
+ xml: "xml", svg: "xml",
+ c: "c", cpp: "cpp", h: "c",
+ cs: "csharp", swift: "swift", kt: "kotlin",
+ blade: "html",
+};
+
+function getLanguageFromFileName(fileName: string): string | undefined {
+ const ext = fileName.split(".").pop()?.toLowerCase();
+ // Handle compound extensions like .blade.php
+ const parts = fileName.split(".");
+ if (parts.length >= 3) {
+ const compound = parts.slice(-2).join(".");
+ if (compound === "blade.php") return "html";
+ }
+ return ext ? extToLanguage[ext] : undefined;
+}
+
function defaultDiffEditorOptions(): MonacoTypes.editor.IDiffEditorOptions {
const opts: MonacoTypes.editor.IDiffEditorOptions = {
scrollBeyondLastLine: false,
@@ -67,7 +95,7 @@ export function DiffViewer({ blockId, original, modified, language, fileName }:
original={original}
modified={modified}
options={editorOpts}
- language={language}
+ language={language ?? getLanguageFromFileName(fileName)}
/>
diff --git a/frontend/app/view/mcpclient/mcpclient.tsx b/frontend/app/view/mcpclient/mcpclient.tsx
new file mode 100644
index 0000000000..b6165ad948
--- /dev/null
+++ b/frontend/app/view/mcpclient/mcpclient.tsx
@@ -0,0 +1,424 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { BlockNodeModel } from "@/app/block/blocktypes";
+import { RpcApi } from "@/app/store/wshclientapi";
+import type { TabModel } from "@/app/store/tab-model";
+import { TabRpcClient } from "@/app/store/wshrpcutil";
+import { WOS } from "@/store/global";
+import { getWebServerEndpoint } from "@/util/endpoints";
+import { fetch } from "@/util/fetchutil";
+import { Atom, atom, useAtomValue } from "jotai";
+import React, { memo, useCallback, useEffect, useState } from "react";
+
+type MCPTool = {
+ name: string;
+ title?: string;
+ description: string;
+ inputSchema: Record;
+};
+
+type MCPServerInfo = {
+ name: string;
+ version: string;
+};
+
+type MCPStatusResponse = {
+ connected: boolean;
+ serverName?: string;
+ serverInfo?: MCPServerInfo;
+ tools?: MCPTool[];
+ error?: string;
+};
+
+type MCPCallLogEntry = {
+ timestamp: string;
+ toolName: string;
+ duration: number;
+ error?: string;
+ resultLen: number;
+ arguments?: Record;
+ result?: string;
+};
+
+type MCPCallResponse = {
+ result?: string;
+ error?: string;
+ duration?: number;
+};
+
+function mcpUrl(path: string): string {
+ return getWebServerEndpoint() + path;
+}
+
+async function fetchMCPStatus(cwd: string): Promise {
+ const resp = await fetch(mcpUrl(`/wave/mcp/status?cwd=${encodeURIComponent(cwd)}`));
+ return resp.json();
+}
+
+async function callMCPTool(cwd: string, toolName: string, args?: Record): Promise {
+ const resp = await fetch(mcpUrl("/wave/mcp/call"), {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ cwd, toolName, arguments: args || {} }),
+ });
+ return resp.json();
+}
+
+async function fetchCallLog(): Promise {
+ const resp = await fetch(mcpUrl("/wave/mcp/calllog"));
+ return resp.json();
+}
+
+// ── Tool Run Dialog ──────────────────────────────────────────────
+
+const ToolRunDialog = memo(
+ ({
+ tool,
+ cwd,
+ onClose,
+ }: {
+ tool: MCPTool;
+ cwd: string;
+ onClose: () => void;
+ }) => {
+ const [argsText, setArgsText] = useState("{}");
+ const [result, setResult] = useState(null);
+ const [running, setRunning] = useState(false);
+
+ const handleRun = useCallback(async () => {
+ setRunning(true);
+ setResult(null);
+ try {
+ const args = JSON.parse(argsText);
+ const resp = await callMCPTool(cwd, tool.name, args);
+ setResult(resp);
+ } catch (e: any) {
+ setResult({ error: e.message });
+ }
+ setRunning(false);
+ }, [cwd, tool.name, argsText]);
+
+ return (
+
+
+
{tool.name}
+
+ Close
+
+
+
{tool.description}
+
+
+ Arguments (JSON)
+
+
+
+ {running ? "Running..." : "Run"}
+
+
+ {result && (
+
+ {result.error ? (
+
+ Error: {result.error}
+
+ ) : (
+
+
+ Result ({result.duration?.toFixed(2)}s)
+
+
+ {result.result}
+
+
+ )}
+
+ )}
+
+ );
+ }
+);
+ToolRunDialog.displayName = "ToolRunDialog";
+
+// ── Call Log Item (expandable) ────────────────────────────────────
+
+const CallLogItem = memo(({ entry }: { entry: MCPCallLogEntry }) => {
+ const [expanded, setExpanded] = useState(false);
+ const time = new Date(entry.timestamp).toLocaleTimeString();
+ const hasDetails = entry.result || entry.error || entry.arguments;
+
+ return (
+
+
hasDetails && setExpanded(!expanded)}
+ >
+ {hasDetails && (
+
+ )}
+ {!hasDetails && }
+ {time}
+ {entry.toolName}
+ {entry.duration.toFixed(2)}s
+ {entry.error ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ {expanded && hasDetails && (
+
+ {entry.arguments && Object.keys(entry.arguments).length > 0 && (
+
+
Arguments:
+
+ {JSON.stringify(entry.arguments, null, 2)}
+
+
+ )}
+ {entry.error && (
+
+
Error:
+
+ {entry.error}
+
+
+ )}
+ {entry.result && (
+
+
+ Result ({entry.resultLen.toLocaleString()} bytes):
+
+
+ {entry.result}
+
+
+ )}
+
+ )}
+
+ );
+});
+CallLogItem.displayName = "CallLogItem";
+
+// ── CWD Input (shown when no CWD is set) ─────────────────────────
+
+function MCPCwdInput({ model }: { model: MCPClientViewModel }) {
+ const [inputCwd, setInputCwd] = useState("");
+
+ const handleConnect = useCallback(() => {
+ if (!inputCwd.trim()) return;
+ RpcApi.SetMetaCommand(TabRpcClient, {
+ oref: WOS.makeORef("block", model.blockId),
+ meta: { "cmd:cwd": inputCwd.trim() },
+ });
+ }, [inputCwd, model.blockId]);
+
+ return (
+
+
+
+ Enter path to a directory with .mcp.json
+
+
+ setInputCwd(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleConnect()}
+ placeholder="/path/to/project"
+ className="flex-1 bg-gray-900 text-gray-200 text-sm font-mono px-3 py-2 rounded border border-gray-600 focus:border-accent-500 focus:outline-none"
+ spellCheck={false}
+ />
+
+ Connect
+
+
+
+ );
+}
+
+// ── Main MCP Client View ─────────────────────────────────────────
+
+function MCPClientView({ model }: ViewComponentProps) {
+ const [status, setStatus] = useState(null);
+ const [callLogEntries, setCallLogEntries] = useState([]);
+ const [activeTool, setActiveTool] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const cwd = useAtomValue(model.cwdAtom);
+
+ const refresh = useCallback(async () => {
+ if (!cwd) {
+ setLoading(false);
+ return;
+ }
+ setLoading(true);
+ try {
+ const [statusResp, logResp] = await Promise.all([fetchMCPStatus(cwd), fetchCallLog()]);
+ setStatus(statusResp);
+ setCallLogEntries(logResp || []);
+ } catch {
+ setStatus({ connected: false, error: "Failed to fetch status" });
+ }
+ setLoading(false);
+ }, [cwd]);
+
+ useEffect(() => {
+ refresh();
+ const interval = setInterval(refresh, 10000);
+ return () => clearInterval(interval);
+ }, [refresh]);
+
+ if (!cwd) {
+ return ;
+ }
+
+ if (loading && !status) {
+ return (
+
+ Connecting to MCP server...
+
+ );
+ }
+
+ return (
+
+ {/* Server Status Header */}
+
+
+
+
+ {status?.serverInfo
+ ? `${status.serverInfo.name} v${status.serverInfo.version}`
+ : status?.serverName || "MCP Server"}
+
+
+
+
+
+
+ {status?.connected ? "Connected" : status?.error || "Disconnected"}
+ {" · "}
+ CWD: {cwd}
+
+
+
+
+ {/* Active Tool Dialog */}
+ {activeTool && (
+
setActiveTool(null)} />
+ )}
+
+ {/* Tools List */}
+ {status?.tools && status.tools.length > 0 && (
+
+
+ Tools ({status.tools.length})
+
+
+ {status.tools.map((tool) => (
+
+
+
+ {tool.name}
+
+
+ {tool.description?.slice(0, 80)}
+ {(tool.description?.length ?? 0) > 80 ? "..." : ""}
+
+
+
setActiveTool(tool)}
+ className="text-accent-400 hover:text-accent-300 text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
+ >
+ Run
+
+
+ ))}
+
+
+ )}
+
+ {/* Call Log */}
+ {callLogEntries.length > 0 && (
+
+
+ Call Log
+
+
+ {[...callLogEntries].reverse().map((entry, idx) => (
+
+ ))}
+
+
+ )}
+
+
+ );
+}
+
+// ── ViewModel ────────────────────────────────────────────────────
+
+class MCPClientViewModel implements ViewModel {
+ viewType: string = "mcpclient";
+ blockId: string;
+ nodeModel: BlockNodeModel;
+ tabModel: TabModel;
+ blockAtom: Atom;
+
+ viewIcon: Atom;
+ viewName: Atom;
+ cwdAtom: Atom;
+
+ constructor({ blockId, nodeModel, tabModel }: ViewModelInitType) {
+ this.blockId = blockId;
+ this.nodeModel = nodeModel;
+ this.tabModel = tabModel;
+
+ this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`);
+ this.viewIcon = atom("plug");
+ this.viewName = atom("MCP Client");
+
+ this.cwdAtom = atom((get) => {
+ const block = get(this.blockAtom);
+ return (block?.meta?.["cmd:cwd"] as string) || "";
+ });
+ }
+
+ get viewComponent(): ViewComponent {
+ return MCPClientView as ViewComponent;
+ }
+}
+
+export { MCPClientViewModel };
diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx
index 39369e4daf..3fed847dac 100644
--- a/frontend/app/view/webview/webview.tsx
+++ b/frontend/app/view/webview/webview.tsx
@@ -1068,8 +1068,19 @@ const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps)
webview.addEventListener("media-paused", handleMediaPaused);
webview.addEventListener("found-in-page", onFoundInPage);
+ // Track page title for AI context
+ const pageTitleHandler = (e: any) => {
+ const title = e.title || "";
+ model.env.rpc.SetMetaCommand(TabRpcClient, {
+ oref: makeORef("block", model.blockId),
+ meta: { "web:title": title } as any,
+ });
+ };
+ webview.addEventListener("page-title-updated", pageTitleHandler);
+
// Clean up event listeners on component unmount
return () => {
+ webview.removeEventListener("page-title-updated", pageTitleHandler);
webview.removeEventListener("did-frame-navigate", navigateListener);
webview.removeEventListener("did-navigate", navigateListener);
webview.removeEventListener("did-navigate-in-page", navigateListener);
diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts
index 193929ef4c..5bfaea589d 100644
--- a/frontend/types/gotypes.d.ts
+++ b/frontend/types/gotypes.d.ts
@@ -724,6 +724,11 @@ declare global {
modifiedcontents64: string;
};
+ // wshrpc.CommandWaveAISessionReadApproveData
+ type CommandWaveAISessionReadApproveData = {
+ path: string;
+ };
+
// wshrpc.CommandWaveAIToolApproveData
type CommandWaveAIToolApproveData = {
toolcallid: string;
@@ -1141,6 +1146,8 @@ declare global {
"waveai:model"?: string;
"waveai:chatid"?: string;
"waveai:widgetcontext"?: boolean;
+ "waveai:mcpcontext"?: boolean;
+ "waveai:mcpcwd"?: string;
"term:*"?: boolean;
"term:fontsize"?: number;
"term:fontfamily"?: string;
@@ -2119,6 +2126,10 @@ declare global {
type WebSelectorOpts = {
all?: boolean;
inner?: boolean;
+ innertext?: boolean;
+ reload?: boolean;
+ highlight?: boolean;
+ execjs?: string;
};
// wconfig.WidgetConfigType
diff --git a/go.mod b/go.mod
index 1e9e2d3663..12340c4ade 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/wavetermdev/waveterm
+module github.com/woveterm/wove
go 1.25.6
@@ -30,7 +30,7 @@ require (
github.com/spf13/cobra v1.10.2
github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b
github.com/wavetermdev/htmltoken v0.2.0
- github.com/wavetermdev/waveterm/tsunami v0.12.3
+ github.com/woveterm/wove/tsunami v0.12.3
golang.org/x/crypto v0.49.0
golang.org/x/mod v0.34.0
golang.org/x/sync v0.20.0
@@ -91,4 +91,4 @@ replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0
replace github.com/creack/pty => github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b
-replace github.com/wavetermdev/waveterm/tsunami => ./tsunami
+replace github.com/woveterm/wove/tsunami => ./tsunami
diff --git a/package.json b/package.json
index 18d04ac3df..3765b16e98 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,16 @@
{
- "name": "waveterm",
+ "name": "wove",
"author": {
- "name": "Command Line Inc",
- "email": "info@commandline.dev"
+ "name": "MITS Sp. z o.o.",
+ "email": "hello@getwove.dev"
},
- "productName": "Wave",
- "description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
+ "productName": "Wove",
+ "description": "AI-first terminal for developers who code, not click",
"license": "Apache-2.0",
- "version": "0.14.3",
- "homepage": "https://waveterm.dev",
+ "version": "0.1.0",
+ "homepage": "https://getwove.dev",
"build": {
- "appId": "dev.commandline.waveterm"
+ "appId": "dev.getwove.wove"
},
"private": true,
"main": "./dist/main/index.js",
diff --git a/pkg/aiusechat/aiplan/aiplan.go b/pkg/aiusechat/aiplan/aiplan.go
new file mode 100644
index 0000000000..4cde01841c
--- /dev/null
+++ b/pkg/aiusechat/aiplan/aiplan.go
@@ -0,0 +1,294 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiplan
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/woveterm/wove/pkg/wavebase"
+)
+
+const (
+ planDirName = "plans"
+ maxResultLen = 2000
+ StatusPending = "pending"
+ StatusRunning = "running"
+ StatusDone = "done"
+ StatusFailed = "failed"
+ StatusSkipped = "skipped"
+)
+
+// PlanStep represents a single step in the plan.
+type PlanStep struct {
+ Id int `json:"id"`
+ Label string `json:"label"`
+ Status string `json:"status"`
+ Details string `json:"details,omitempty"`
+ Result string `json:"result,omitempty"`
+ Error string `json:"error,omitempty"`
+ DoneAt string `json:"doneAt,omitempty"`
+}
+
+// Plan represents a multi-step execution plan for AI.
+type Plan struct {
+ TabId string `json:"tabId"`
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ CreatedAt string `json:"createdAt"`
+ UpdatedAt string `json:"updatedAt"`
+ Steps []PlanStep `json:"steps"`
+}
+
+var (
+ plansMu sync.Mutex
+)
+
+func getPlanDir() string {
+ return filepath.Join(wavebase.GetWaveDataDir(), planDirName)
+}
+
+func getPlanFilePath(tabId string) string {
+ return filepath.Join(getPlanDir(), tabId+".json")
+}
+
+// CreatePlan creates a new plan (without details per step).
+func CreatePlan(tabId string, name string, description string, stepLabels []string) (*Plan, error) {
+ details := make([]string, len(stepLabels))
+ return CreatePlanWithDetails(tabId, name, description, stepLabels, details)
+}
+
+// CreatePlanWithDetails creates a new plan with optional details per step.
+func CreatePlanWithDetails(tabId string, name string, description string, stepLabels []string, stepDetails []string) (*Plan, error) {
+ plansMu.Lock()
+ defer plansMu.Unlock()
+
+ if err := os.MkdirAll(getPlanDir(), 0755); err != nil {
+ return nil, fmt.Errorf("creating plans dir: %w", err)
+ }
+
+ // Auto-append testing and lint steps if not already present
+ hasTestStep := false
+ hasLintStep := false
+ for _, label := range stepLabels {
+ lower := strings.ToLower(label)
+ if strings.Contains(lower, "test") && (strings.Contains(lower, "write") || strings.Contains(lower, "create")) {
+ hasTestStep = true
+ }
+ if strings.Contains(lower, "lint") || strings.Contains(lower, "pint") || strings.Contains(lower, "format") {
+ hasLintStep = true
+ }
+ }
+ if !hasLintStep {
+ stepLabels = append(stepLabels, "Run syntax check (php -l) and lint/format (pint) on all modified files")
+ }
+ // Review step: verify code against project conventions
+ stepLabels = append(stepLabels, "Call wave_utils(action='project_instructions') then review all created files against those rules - fix any violations")
+ if !hasTestStep {
+ stepLabels = append(stepLabels, "Write NEW test file with happy path, edge cases, and business logic tests")
+ }
+ // Always end with running tests
+ stepLabels = append(stepLabels, "Run all tests and verify they pass")
+
+ steps := make([]PlanStep, len(stepLabels))
+ for i, label := range stepLabels {
+ steps[i] = PlanStep{
+ Id: i + 1,
+ Label: label,
+ Status: StatusPending,
+ }
+ if i < len(stepDetails) && stepDetails[i] != "" {
+ steps[i].Details = stepDetails[i]
+ }
+ }
+
+ now := time.Now().Format(time.RFC3339)
+ plan := &Plan{
+ TabId: tabId,
+ Name: name,
+ Description: description,
+ CreatedAt: now,
+ UpdatedAt: now,
+ Steps: steps,
+ }
+
+ if err := savePlanLocked(plan); err != nil {
+ return nil, err
+ }
+ return plan, nil
+}
+
+// GetPlan loads the current plan for a tab.
+func GetPlan(tabId string) *Plan {
+ plansMu.Lock()
+ defer plansMu.Unlock()
+ return loadPlanLocked(tabId)
+}
+
+// UpdateStep marks a step with a new status and optional result.
+func UpdateStep(tabId string, stepId int, status string, result string, errMsg string) (*Plan, error) {
+ plansMu.Lock()
+ defer plansMu.Unlock()
+
+ plan := loadPlanLocked(tabId)
+ if plan == nil {
+ return nil, fmt.Errorf("no plan found for tab %s", tabId)
+ }
+
+ found := false
+ for i := range plan.Steps {
+ if plan.Steps[i].Id == stepId {
+ plan.Steps[i].Status = status
+ if result != "" {
+ if len(result) > maxResultLen {
+ result = result[:maxResultLen] + "..."
+ }
+ plan.Steps[i].Result = result
+ }
+ if errMsg != "" {
+ plan.Steps[i].Error = errMsg
+ }
+ if status == StatusDone || status == StatusFailed || status == StatusSkipped {
+ plan.Steps[i].DoneAt = time.Now().Format(time.RFC3339)
+ }
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ return nil, fmt.Errorf("step %d not found in plan", stepId)
+ }
+
+ plan.UpdatedAt = time.Now().Format(time.RFC3339)
+ if err := savePlanLocked(plan); err != nil {
+ return nil, err
+ }
+ return plan, nil
+}
+
+// DeletePlan removes the plan for a tab.
+func DeletePlan(tabId string) {
+ plansMu.Lock()
+ defer plansMu.Unlock()
+ os.Remove(getPlanFilePath(tabId))
+}
+
+// GetNextPendingStep returns the next step that needs to be executed.
+func GetNextPendingStep(plan *Plan) *PlanStep {
+ if plan == nil {
+ return nil
+ }
+ for i := range plan.Steps {
+ if plan.Steps[i].Status == StatusPending {
+ return &plan.Steps[i]
+ }
+ }
+ return nil
+}
+
+// IsComplete returns true if all steps are done/failed/skipped.
+func IsComplete(plan *Plan) bool {
+ if plan == nil {
+ return true
+ }
+ for _, step := range plan.Steps {
+ if step.Status == StatusPending || step.Status == StatusRunning {
+ return false
+ }
+ }
+ return true
+}
+
+// FormatPlanStatus returns a human-readable summary of the plan for AI context.
+func FormatPlanStatus(plan *Plan) string {
+ if plan == nil {
+ return ""
+ }
+
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("\n", plan.Name))
+ if plan.Description != "" {
+ sb.WriteString(fmt.Sprintf("Description: %s\n", plan.Description))
+ }
+
+ doneCount := 0
+ totalCount := len(plan.Steps)
+ for _, step := range plan.Steps {
+ if step.Status == StatusDone || step.Status == StatusFailed || step.Status == StatusSkipped {
+ doneCount++
+ }
+ }
+ sb.WriteString(fmt.Sprintf("Progress: %d/%d steps completed\n\n", doneCount, totalCount))
+
+ for _, step := range plan.Steps {
+ icon := "[ ]"
+ switch step.Status {
+ case StatusDone:
+ icon = "[x]"
+ case StatusFailed:
+ icon = "[!]"
+ case StatusRunning:
+ icon = "[>]"
+ case StatusSkipped:
+ icon = "[-]"
+ }
+
+ sb.WriteString(fmt.Sprintf("%s %d. %s", icon, step.Id, step.Label))
+ if step.Details != "" {
+ sb.WriteString(fmt.Sprintf("\n %s", step.Details))
+ }
+ if step.Result != "" {
+ // Show abbreviated result
+ result := step.Result
+ if len(result) > 200 {
+ result = result[:200] + "..."
+ }
+ sb.WriteString(fmt.Sprintf(" - %s", result))
+ }
+ if step.Error != "" {
+ sb.WriteString(fmt.Sprintf(" [ERROR: %s]", step.Error))
+ }
+ sb.WriteString("\n")
+ }
+
+ next := GetNextPendingStep(plan)
+ if next != nil {
+ sb.WriteString(fmt.Sprintf("\nNext step: #%d %s\n", next.Id, next.Label))
+ if next.Details != "" {
+ sb.WriteString(fmt.Sprintf("Details: %s\n", next.Details))
+ }
+ sb.WriteString("IMPORTANT: This plan was started in a previous session. Continue executing from this step now. Do not restart the plan or re-create completed steps. Call wave_utils(action='plan_update') when done.\n")
+ } else {
+ sb.WriteString("\nAll steps completed. Summarize the results for the user.\n")
+ }
+
+ sb.WriteString(" ")
+ return sb.String()
+}
+
+func loadPlanLocked(tabId string) *Plan {
+ data, err := os.ReadFile(getPlanFilePath(tabId))
+ if err != nil {
+ return nil
+ }
+ var plan Plan
+ if err := json.Unmarshal(data, &plan); err != nil {
+ return nil
+ }
+ return &plan
+}
+
+func savePlanLocked(plan *Plan) error {
+ data, err := json.MarshalIndent(plan, "", " ")
+ if err != nil {
+ return fmt.Errorf("marshaling plan: %w", err)
+ }
+ return os.WriteFile(getPlanFilePath(plan.TabId), data, 0644)
+}
diff --git a/pkg/aiusechat/aiplan/handler.go b/pkg/aiusechat/aiplan/handler.go
new file mode 100644
index 0000000000..b7221a1354
--- /dev/null
+++ b/pkg/aiusechat/aiplan/handler.go
@@ -0,0 +1,38 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiplan
+
+import (
+ "encoding/json"
+ "net/http"
+)
+
+// HandlePlanStatus handles GET /wave/plan/status?tabid=...
+func HandlePlanStatus(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ tabId := r.URL.Query().Get("tabid")
+ if tabId == "" {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{"plan": nil})
+ return
+ }
+
+ plan := GetPlan(tabId)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{"plan": plan})
+}
+
+// HandlePlanDelete handles GET /wave/plan/delete?tabid=...
+func HandlePlanDelete(w http.ResponseWriter, r *http.Request) {
+ tabId := r.URL.Query().Get("tabid")
+ if tabId != "" {
+ DeletePlan(tabId)
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{"ok": true})
+}
diff --git a/pkg/aiusechat/aiutil/aiutil.go b/pkg/aiusechat/aiutil/aiutil.go
index 075dd58e7b..91c6b268a0 100644
--- a/pkg/aiusechat/aiutil/aiutil.go
+++ b/pkg/aiusechat/aiutil/aiutil.go
@@ -17,10 +17,10 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/web/sse"
)
// ExtractXmlAttribute extracts an attribute value from an XML-like tag.
diff --git a/pkg/aiusechat/anthropic/anthropic-backend.go b/pkg/aiusechat/anthropic/anthropic-backend.go
index 02070b1bf8..b2bbb63d3b 100644
--- a/pkg/aiusechat/anthropic/anthropic-backend.go
+++ b/pkg/aiusechat/anthropic/anthropic-backend.go
@@ -18,12 +18,12 @@ import (
"github.com/google/uuid"
"github.com/launchdarkly/eventsource"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/logutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/logutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/web/sse"
)
const (
diff --git a/pkg/aiusechat/anthropic/anthropic-backend_test.go b/pkg/aiusechat/anthropic/anthropic-backend_test.go
index 71e89bfb2f..32b7c5a3ab 100644
--- a/pkg/aiusechat/anthropic/anthropic-backend_test.go
+++ b/pkg/aiusechat/anthropic/anthropic-backend_test.go
@@ -6,8 +6,8 @@ package anthropic
import (
"testing"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
)
func TestConvertPartsToAnthropicBlocks_TextOnly(t *testing.T) {
diff --git a/pkg/aiusechat/anthropic/anthropic-convertmessage.go b/pkg/aiusechat/anthropic/anthropic-convertmessage.go
index 552cc8080c..e178000336 100644
--- a/pkg/aiusechat/anthropic/anthropic-convertmessage.go
+++ b/pkg/aiusechat/anthropic/anthropic-convertmessage.go
@@ -17,11 +17,11 @@ import (
"strings"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/logutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/logutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
)
// these conversions are based off the anthropic spec
@@ -71,6 +71,20 @@ func buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage
}
}
+ // inject chatOpts.MCPState as a "text" block at the END of the LAST "user" message found
+ if chatOpts.MCPState != "" {
+ for i := len(convertedMsgs) - 1; i >= 0; i-- {
+ if convertedMsgs[i].Role == "user" {
+ mcpStateBlock := anthropicMessageContentBlock{
+ Type: "text",
+ Text: chatOpts.MCPState,
+ }
+ convertedMsgs[i].Content = append(convertedMsgs[i].Content, mcpStateBlock)
+ break
+ }
+ }
+ }
+
// inject chatOpts.PlatformInfo, AppStaticFiles, and AppGoFile as "text" blocks at the END of the LAST "user" message found (append to Content)
if chatOpts.PlatformInfo != "" || chatOpts.AppStaticFiles != "" || chatOpts.AppGoFile != "" {
// Find the last "user" message
@@ -130,6 +144,10 @@ func buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage
cleanedTool := tool.Clean()
reqBody.Tools = append(reqBody.Tools, cleanedTool)
}
+ for _, tool := range chatOpts.MCPTools {
+ cleanedTool := tool.Clean()
+ reqBody.Tools = append(reqBody.Tools, cleanedTool)
+ }
if chatOpts.AllowNativeWebSearch {
reqBody.Tools = append(reqBody.Tools, &anthropicWebSearchTool{Type: "web_search_20250305", Name: "web_search"})
}
diff --git a/pkg/aiusechat/chatstore/chatstore.go b/pkg/aiusechat/chatstore/chatstore.go
index 4abe26ba62..dc8a1cd7ea 100644
--- a/pkg/aiusechat/chatstore/chatstore.go
+++ b/pkg/aiusechat/chatstore/chatstore.go
@@ -8,7 +8,7 @@ import (
"slices"
"sync"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
)
type ChatStore struct {
@@ -49,6 +49,18 @@ func (cs *ChatStore) Delete(chatId string) {
delete(cs.chats, chatId)
}
+// GetAll returns a copy of all chats in the store.
+func (cs *ChatStore) GetAll() map[string]*uctypes.AIChat {
+ cs.lock.Lock()
+ defer cs.lock.Unlock()
+
+ result := make(map[string]*uctypes.AIChat, len(cs.chats))
+ for k, v := range cs.chats {
+ result[k] = v
+ }
+ return result
+}
+
func (cs *ChatStore) CountUserMessages(chatId string) int {
cs.lock.Lock()
defer cs.lock.Unlock()
diff --git a/pkg/aiusechat/gemini/gemini-backend.go b/pkg/aiusechat/gemini/gemini-backend.go
index 728df59a4f..6a76bb3527 100644
--- a/pkg/aiusechat/gemini/gemini-backend.go
+++ b/pkg/aiusechat/gemini/gemini-backend.go
@@ -17,12 +17,12 @@ import (
"github.com/google/uuid"
"github.com/launchdarkly/eventsource"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/web/sse"
)
// ensureAltSse ensures the ?alt=sse query parameter is set on the endpoint
@@ -107,6 +107,7 @@ func buildGeminiHTTPRequest(ctx context.Context, contents []GeminiContent, chatO
var allTools []uctypes.ToolDefinition
allTools = append(allTools, chatOpts.Tools...)
allTools = append(allTools, chatOpts.TabTools...)
+ allTools = append(allTools, chatOpts.MCPTools...)
if len(allTools) > 0 {
var functionDeclarations []GeminiFunctionDeclaration
@@ -133,6 +134,9 @@ func buildGeminiHTTPRequest(ctx context.Context, contents []GeminiContent, chatO
if chatOpts.TabState != "" {
appendPartToLastUserMessage(reqBody.Contents, chatOpts.TabState)
}
+ if chatOpts.MCPState != "" {
+ appendPartToLastUserMessage(reqBody.Contents, chatOpts.MCPState)
+ }
if chatOpts.PlatformInfo != "" {
appendPartToLastUserMessage(reqBody.Contents, "\n"+chatOpts.PlatformInfo+"\n ")
}
diff --git a/pkg/aiusechat/gemini/gemini-convertmessage.go b/pkg/aiusechat/gemini/gemini-convertmessage.go
index 02cd809c44..7bbd2401d6 100644
--- a/pkg/aiusechat/gemini/gemini-convertmessage.go
+++ b/pkg/aiusechat/gemini/gemini-convertmessage.go
@@ -12,10 +12,10 @@ import (
"strings"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
// cleanSchemaForGemini removes fields from JSON Schema that Gemini doesn't accept
diff --git a/pkg/aiusechat/gemini/gemini-types.go b/pkg/aiusechat/gemini/gemini-types.go
index e873abbb4c..db1cf577ce 100644
--- a/pkg/aiusechat/gemini/gemini-types.go
+++ b/pkg/aiusechat/gemini/gemini-types.go
@@ -4,7 +4,7 @@
package gemini
import (
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
)
const (
diff --git a/pkg/aiusechat/google/google-summarize.go b/pkg/aiusechat/google/google-summarize.go
index 67d3cd3eb5..1132cd6ff2 100644
--- a/pkg/aiusechat/google/google-summarize.go
+++ b/pkg/aiusechat/google/google-summarize.go
@@ -11,7 +11,7 @@ import (
"strings"
"github.com/google/generative-ai-go/genai"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
"google.golang.org/api/option"
)
diff --git a/pkg/aiusechat/openai/openai-backend.go b/pkg/aiusechat/openai/openai-backend.go
index dfb14b70df..740da577d6 100644
--- a/pkg/aiusechat/openai/openai-backend.go
+++ b/pkg/aiusechat/openai/openai-backend.go
@@ -16,12 +16,12 @@ import (
"github.com/google/uuid"
"github.com/launchdarkly/eventsource"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/logutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/logutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/web/sse"
)
// sanitizeHostnameInError removes the Wave cloud hostname from error messages
diff --git a/pkg/aiusechat/openai/openai-convertmessage.go b/pkg/aiusechat/openai/openai-convertmessage.go
index 045cc65cf2..dfca1661ed 100644
--- a/pkg/aiusechat/openai/openai-convertmessage.go
+++ b/pkg/aiusechat/openai/openai-convertmessage.go
@@ -14,9 +14,9 @@ import (
"strings"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const (
@@ -221,6 +221,9 @@ func buildOpenAIHTTPRequest(ctx context.Context, inputs []any, chatOpts uctypes.
if chatOpts.TabState != "" {
appendToLastUserMessage(inputs, chatOpts.TabState)
}
+ if chatOpts.MCPState != "" {
+ appendToLastUserMessage(inputs, chatOpts.MCPState)
+ }
if chatOpts.PlatformInfo != "" {
appendToLastUserMessage(inputs, "\n"+chatOpts.PlatformInfo+"\n ")
}
@@ -263,6 +266,10 @@ func buildOpenAIHTTPRequest(ctx context.Context, inputs []any, chatOpts uctypes.
convertedTool := ConvertToolDefinitionToOpenAI(tool)
reqBody.Tools = append(reqBody.Tools, convertedTool)
}
+ for _, tool := range chatOpts.MCPTools {
+ convertedTool := ConvertToolDefinitionToOpenAI(tool)
+ reqBody.Tools = append(reqBody.Tools, convertedTool)
+ }
// Add native web search tool if enabled
if chatOpts.AllowNativeWebSearch {
diff --git a/pkg/aiusechat/openai/openai-util.go b/pkg/aiusechat/openai/openai-util.go
index 2e5b6a7f06..fa33f05ae9 100644
--- a/pkg/aiusechat/openai/openai-util.go
+++ b/pkg/aiusechat/openai/openai-util.go
@@ -7,7 +7,7 @@ import (
"encoding/json"
"log"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
func debugPrintInput(idx int, input any) {
diff --git a/pkg/aiusechat/openaichat/openaichat-backend.go b/pkg/aiusechat/openaichat/openaichat-backend.go
index 635f334873..1c34c93835 100644
--- a/pkg/aiusechat/openaichat/openaichat-backend.go
+++ b/pkg/aiusechat/openaichat/openaichat-backend.go
@@ -16,10 +16,10 @@ import (
"github.com/google/uuid"
"github.com/launchdarkly/eventsource"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/web/sse"
)
// RunChatStep executes a chat step using the chat completions API
diff --git a/pkg/aiusechat/openaichat/openaichat-convertmessage.go b/pkg/aiusechat/openaichat/openaichat-convertmessage.go
index 90fe8c9f1f..917cfe4bde 100644
--- a/pkg/aiusechat/openaichat/openaichat-convertmessage.go
+++ b/pkg/aiusechat/openaichat/openaichat-convertmessage.go
@@ -14,10 +14,10 @@ import (
"slices"
"strings"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const (
@@ -96,6 +96,9 @@ func buildChatHTTPRequest(ctx context.Context, messages []ChatRequestMessage, ch
if chatOpts.TabState != "" {
appendToLastUserMessage(finalMessages, chatOpts.TabState)
}
+ if chatOpts.MCPState != "" {
+ appendToLastUserMessage(finalMessages, chatOpts.MCPState)
+ }
if chatOpts.PlatformInfo != "" {
appendToLastUserMessage(finalMessages, "\n"+chatOpts.PlatformInfo+"\n ")
}
@@ -121,6 +124,7 @@ func buildChatHTTPRequest(ctx context.Context, messages []ChatRequestMessage, ch
if opts.HasCapability(uctypes.AICapabilityTools) {
allTools = append(allTools, chatOpts.Tools...)
allTools = append(allTools, chatOpts.TabTools...)
+ allTools = append(allTools, chatOpts.MCPTools...)
if len(allTools) > 0 {
reqBody.Tools = convertToolDefinitions(allTools, opts.Capabilities)
}
diff --git a/pkg/aiusechat/openaichat/openaichat-types.go b/pkg/aiusechat/openaichat/openaichat-types.go
index 18d28e3b20..cbb6f01a79 100644
--- a/pkg/aiusechat/openaichat/openaichat-types.go
+++ b/pkg/aiusechat/openaichat/openaichat-types.go
@@ -7,7 +7,7 @@ import (
"bytes"
"encoding/json"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
)
// OpenAI Chat Completions API types (simplified)
diff --git a/pkg/aiusechat/projectctx/projectctx.go b/pkg/aiusechat/projectctx/projectctx.go
new file mode 100644
index 0000000000..cbd094b2b3
--- /dev/null
+++ b/pkg/aiusechat/projectctx/projectctx.go
@@ -0,0 +1,412 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package projectctx
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// Supported instruction file names, checked in order of priority.
+var instructionFiles = []string{
+ "WAVE.md",
+ "CLAUDE.md",
+ "GEMINI.md",
+ "AGENTS.md",
+ ".cursorrules",
+ ".github/copilot-instructions.md",
+}
+
+// Section represents a parsed section of the instructions file.
+type Section struct {
+ Heading string
+ Content string
+ Tags []string // inferred technology tags
+}
+
+// ProjectInstructions holds parsed project instructions.
+type ProjectInstructions struct {
+ FilePath string
+ FileName string
+ RawSize int
+ Sections []Section
+ ProjectInfo string // first section (Project/overview)
+}
+
+// FindInstructionsFile looks for a known instructions file in the given directory.
+// Returns the first found file path (for backward compat / system prompt check).
+func FindInstructionsFile(dir string) string {
+ for _, name := range instructionFiles {
+ path := filepath.Join(dir, name)
+ if _, err := os.Stat(path); err == nil {
+ return path
+ }
+ }
+ return ""
+}
+
+// FindAllInstructionsFiles returns all existing instructions files in the directory.
+// WAVE.md is always first (highest priority), then others in order.
+func FindAllInstructionsFiles(dir string) []string {
+ var found []string
+ for _, name := range instructionFiles {
+ path := filepath.Join(dir, name)
+ if _, err := os.Stat(path); err == nil {
+ found = append(found, path)
+ }
+ }
+ return found
+}
+
+// ParseInstructions reads and parses an instructions file into sections.
+func ParseInstructions(filePath string) (*ProjectInstructions, error) {
+ data, err := os.ReadFile(filePath)
+ if err != nil {
+ return nil, fmt.Errorf("reading instructions: %w", err)
+ }
+
+ content := string(data)
+ lines := strings.Split(content, "\n")
+
+ pi := &ProjectInstructions{
+ FilePath: filePath,
+ FileName: filepath.Base(filePath),
+ RawSize: len(data),
+ }
+
+ var currentSection *Section
+ var sectionLines []string
+
+ flushSection := func() {
+ if currentSection != nil {
+ currentSection.Content = strings.TrimSpace(strings.Join(sectionLines, "\n"))
+ currentSection.Tags = inferTags(currentSection.Heading, currentSection.Content)
+ pi.Sections = append(pi.Sections, *currentSection)
+ }
+ }
+
+ for _, line := range lines {
+ if strings.HasPrefix(line, "## ") {
+ flushSection()
+ heading := strings.TrimPrefix(line, "## ")
+ currentSection = &Section{Heading: heading}
+ sectionLines = nil
+ } else if strings.HasPrefix(line, "# ") && currentSection == nil {
+ // Top-level heading, skip but capture as project info start
+ continue
+ } else {
+ sectionLines = append(sectionLines, line)
+ }
+ }
+ flushSection()
+
+ // Extract project info from first section
+ if len(pi.Sections) > 0 && isOverviewSection(pi.Sections[0].Heading) {
+ pi.ProjectInfo = pi.Sections[0].Content
+ }
+
+ return pi, nil
+}
+
+// GetFilteredContext returns sections relevant to the given file extension/technology.
+// Always includes: Project overview, Architecture, Conventions.
+// Adds technology-specific sections based on the file being edited.
+func GetFilteredContext(pi *ProjectInstructions, fileExt string) string {
+ if pi == nil || len(pi.Sections) == 0 {
+ return ""
+ }
+
+ techTags := extToTags(fileExt)
+ var sb strings.Builder
+ sb.WriteString(fmt.Sprintf("\n", pi.FileName))
+
+ for _, section := range pi.Sections {
+ if shouldInclude(section, techTags) {
+ sb.WriteString(fmt.Sprintf("## %s\n", section.Heading))
+ sb.WriteString(section.Content)
+ sb.WriteString("\n\n")
+ }
+ }
+
+ sb.WriteString(" ")
+ result := sb.String()
+
+ // Truncate if too long
+ const maxLen = 8000
+ if len(result) > maxLen {
+ result = result[:maxLen] + "\n... [truncated]\n"
+ }
+
+ return result
+}
+
+// GetFullContext returns all sections (with truncation).
+func GetFullContext(pi *ProjectInstructions) string {
+ return GetFilteredContext(pi, "")
+}
+
+// alwaysInclude are section heading keywords that are always relevant.
+var alwaysIncludeKeywords = []string{
+ "project", "stack", "architektura", "architecture",
+ "conventions", "konwencje", "replies", "structure",
+ "foundational", "overview", "opis",
+}
+
+func isOverviewSection(heading string) bool {
+ h := strings.ToLower(heading)
+ return strings.Contains(h, "project") || strings.Contains(h, "overview") ||
+ strings.Contains(h, "opis") || strings.Contains(h, "stack")
+}
+
+// SectionMatchesExt checks if a section is relevant to the given file extension.
+func SectionMatchesExt(section Section, fileExt string) bool {
+ techTags := extToTags(fileExt)
+ return shouldInclude(section, techTags)
+}
+
+func shouldInclude(section Section, techTags []string) bool {
+ h := strings.ToLower(section.Heading)
+
+ // Always include core sections
+ for _, kw := range alwaysIncludeKeywords {
+ if strings.Contains(h, kw) {
+ return true
+ }
+ }
+
+ // If no tech filter, include everything
+ if len(techTags) == 0 {
+ return true
+ }
+
+ // Include if section matches any tech tag
+ for _, tag := range techTags {
+ for _, sectionTag := range section.Tags {
+ if tag == sectionTag {
+ return true
+ }
+ }
+ // Also check heading directly
+ if strings.Contains(h, tag) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// inferTags extracts technology tags from section heading and content.
+func inferTags(heading string, content string) []string {
+ combined := strings.ToLower(heading + " " + content[:min(len(content), 500)])
+ var tags []string
+
+ tagKeywords := map[string][]string{
+ "php": {"php", "laravel", "artisan", "composer", "eloquent", "blade", "pint"},
+ "vue": {"vue", "inertia", "v-model", "v-if", "composition api"},
+ "javascript": {"javascript", "js", "node", "npm", "vite", "typescript", "ts"},
+ "css": {"css", "tailwind", "scss", "sass", "less"},
+ "database": {"database", "mysql", "migration", "eloquent", "query", "sql", "db"},
+ "docker": {"docker", "container", "compose"},
+ "testing": {"test", "phpunit", "pest", "jest", "vitest"},
+ "api": {"api", "endpoint", "route", "controller", "rest"},
+ "auth": {"auth", "permission", "role", "guard", "sanctum"},
+ "frontend": {"frontend", "component", "layout", "ui", "inertia"},
+ "backend": {"backend", "controller", "middleware", "service", "job", "queue"},
+ }
+
+ for tag, keywords := range tagKeywords {
+ for _, kw := range keywords {
+ if strings.Contains(combined, kw) {
+ tags = append(tags, tag)
+ break
+ }
+ }
+ }
+
+ return tags
+}
+
+// extToTags maps file extension to relevant technology tags.
+func extToTags(ext string) []string {
+ ext = strings.TrimPrefix(strings.ToLower(ext), ".")
+ switch ext {
+ case "php":
+ return []string{"php", "backend", "database", "api"}
+ case "vue":
+ return []string{"vue", "javascript", "frontend", "css"}
+ case "ts", "tsx", "js", "jsx":
+ return []string{"javascript", "frontend"}
+ case "css", "scss", "less":
+ return []string{"css", "frontend"}
+ case "sql":
+ return []string{"database"}
+ case "yml", "yaml":
+ return []string{"docker"}
+ case "blade.php":
+ return []string{"php", "frontend"}
+ default:
+ return nil // no filter = include all
+ }
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+// criticalKeywords are patterns that indicate mandatory rules the AI must always follow.
+var criticalKeywords = []string{
+ "must", "always", "never", "required", "mandatory", "enforce",
+ "every change", "after change", "before commit",
+ "pint", "lint", "format", "test",
+}
+
+// ExtractCriticalRules scans all instruction files in the directory and extracts
+// short, mandatory rules (lines containing critical keywords).
+// Returns a compact string (~100-200 tokens) injected into every system prompt.
+func ExtractCriticalRules(dir string) string {
+ files := FindAllInstructionsFiles(dir)
+ if len(files) == 0 {
+ return ""
+ }
+
+ var rules []string
+ seen := make(map[string]bool)
+
+ for _, filePath := range files {
+ pi, err := ParseInstructions(filePath)
+ if err != nil {
+ continue
+ }
+ for _, section := range pi.Sections {
+ lines := strings.Split(section.Content, "\n")
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if line == "" || len(line) < 10 || len(line) > 200 {
+ continue
+ }
+ lineLower := strings.ToLower(line)
+ for _, kw := range criticalKeywords {
+ if strings.Contains(lineLower, kw) {
+ // Clean up markdown formatting
+ clean := strings.TrimLeft(line, "- *>")
+ clean = strings.TrimSpace(clean)
+ if clean != "" && !seen[clean] {
+ seen[clean] = true
+ rules = append(rules, clean)
+ }
+ break
+ }
+ }
+ }
+ }
+ }
+
+ if len(rules) == 0 {
+ return ""
+ }
+
+ // Limit to most important rules
+ if len(rules) > 10 {
+ rules = rules[:10]
+ }
+
+ return "\n" + strings.Join(rules, "\n") + "\n "
+}
+
+// ExtractProjectStack extracts the project overview/stack section from instruction files.
+// Returns a compact string with project name, tech stack, and key architectural decisions.
+func ExtractProjectStack(dir string) string {
+ files := FindAllInstructionsFiles(dir)
+ if len(files) == 0 {
+ return ""
+ }
+
+ for _, filePath := range files {
+ pi, err := ParseInstructions(filePath)
+ if err != nil || pi == nil {
+ continue
+ }
+ // Find first overview section
+ for _, section := range pi.Sections {
+ if isOverviewSection(section.Heading) {
+ content := section.Content
+ // Truncate to keep it compact
+ if len(content) > 500 {
+ content = content[:500] + "..."
+ }
+ return "\n" + content + "\n "
+ }
+ }
+ }
+ return ""
+}
+
+// skipDirs are directories to skip when building project tree.
+var skipDirs = map[string]bool{
+ "node_modules": true, "vendor": true, ".git": true,
+ ".idea": true, ".vscode": true, "dist": true,
+ "build": true, "storage": true, ".next": true,
+ "__pycache__": true, ".cache": true, "tmp": true,
+}
+
+// GetProjectTree returns a compact directory tree of the project (max depth levels).
+// Injected on first message so AI knows the project structure.
+func GetProjectTree(dir string, maxDepth int) string {
+ var sb strings.Builder
+ sb.WriteString("\n")
+ buildTree(&sb, dir, "", 0, maxDepth)
+ sb.WriteString(" ")
+
+ result := sb.String()
+ // Limit size to avoid eating too many tokens
+ const maxLen = 3000
+ if len(result) > maxLen {
+ result = result[:maxLen] + "\n...\n"
+ }
+ return result
+}
+
+func buildTree(sb *strings.Builder, dir string, prefix string, depth int, maxDepth int) {
+ if depth >= maxDepth {
+ return
+ }
+
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return
+ }
+
+ // Separate dirs and files
+ var dirs []os.DirEntry
+ var files []os.DirEntry
+ for _, e := range entries {
+ name := e.Name()
+ if strings.HasPrefix(name, ".") && depth == 0 && e.IsDir() {
+ continue // skip hidden dirs at root
+ }
+ if e.IsDir() {
+ if !skipDirs[name] {
+ dirs = append(dirs, e)
+ }
+ } else {
+ files = append(files, e)
+ }
+ }
+
+ // Show files at this level (just names, no details)
+ for _, f := range files {
+ sb.WriteString(prefix + f.Name() + "\n")
+ }
+
+ // Recurse into dirs
+ for _, d := range dirs {
+ sb.WriteString(prefix + d.Name() + "/\n")
+ buildTree(sb, filepath.Join(dir, d.Name()), prefix+" ", depth+1, maxDepth)
+ }
+}
diff --git a/pkg/aiusechat/sessionapproval.go b/pkg/aiusechat/sessionapproval.go
new file mode 100644
index 0000000000..3e78708510
--- /dev/null
+++ b/pkg/aiusechat/sessionapproval.go
@@ -0,0 +1,138 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiusechat
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "github.com/woveterm/wove/pkg/util/logutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+)
+
+// SessionApprovalRegistry tracks paths that the user has approved for reading
+// during the current session. This is in-memory only and resets when the app restarts.
+type SessionApprovalRegistry struct {
+ mu sync.RWMutex
+ approvedPaths map[string]bool // set of approved directory prefixes
+}
+
+var globalSessionApproval = &SessionApprovalRegistry{
+ approvedPaths: make(map[string]bool),
+}
+
+// canonicalizePath expands ~, cleans, and resolves symlinks for a path.
+// Falls back to cleaned path if symlink resolution fails (e.g. path doesn't exist yet).
+func canonicalizePath(rawPath string) string {
+ expanded, err := wavebase.ExpandHomeDir(rawPath)
+ if err != nil {
+ expanded = rawPath
+ }
+ cleaned := filepath.Clean(expanded)
+ resolved, err := filepath.EvalSymlinks(cleaned)
+ if err != nil {
+ return cleaned
+ }
+ return resolved
+}
+
+// AddSessionReadApproval adds a directory path to the session-level read approval list.
+// All files under this directory (and subdirectories) will be auto-approved for reading.
+// The path is canonicalized (symlinks resolved) to prevent bypass via symlinked directories.
+func AddSessionReadApproval(dirPath string) {
+ canonical := canonicalizePath(dirPath)
+ if isSensitivePath(canonical) {
+ logutil.DevPrintf("session read approval rejected (sensitive path): %s\n", canonical)
+ return
+ }
+ if !strings.HasSuffix(canonical, string(filepath.Separator)) {
+ canonical += string(filepath.Separator)
+ }
+ logutil.DevPrintf("session read approval added: %s\n", canonical)
+ globalSessionApproval.mu.Lock()
+ defer globalSessionApproval.mu.Unlock()
+ globalSessionApproval.approvedPaths[canonical] = true
+}
+
+// isSensitivePath checks if a path is or falls under a sensitive directory
+// that should never be auto-approved, even with session approval.
+func isSensitivePath(expandedPath string) bool {
+ homeDir := os.Getenv("HOME")
+ if homeDir == "" {
+ homeDir = os.Getenv("USERPROFILE")
+ }
+ cleanPath := filepath.Clean(expandedPath)
+
+ sensitiveDirs := []string{
+ filepath.Join(homeDir, ".ssh"),
+ filepath.Join(homeDir, ".aws"),
+ filepath.Join(homeDir, ".gnupg"),
+ filepath.Join(homeDir, ".password-store"),
+ filepath.Join(homeDir, ".secrets"),
+ filepath.Join(homeDir, ".kube"),
+ filepath.Join(homeDir, "Library", "Keychains"),
+ "/Library/Keychains",
+ "/etc/sudoers.d",
+ }
+
+ for _, dir := range sensitiveDirs {
+ dirWithSep := dir + string(filepath.Separator)
+ if cleanPath == dir || strings.HasPrefix(cleanPath, dirWithSep) {
+ return true
+ }
+ }
+
+ if localAppData := os.Getenv("LOCALAPPDATA"); localAppData != "" {
+ credPath := filepath.Join(localAppData, "Microsoft", "Credentials")
+ if cleanPath == credPath || strings.HasPrefix(cleanPath, credPath+string(filepath.Separator)) {
+ return true
+ }
+ }
+ if appData := os.Getenv("APPDATA"); appData != "" {
+ credPath := filepath.Join(appData, "Microsoft", "Credentials")
+ if cleanPath == credPath || strings.HasPrefix(cleanPath, credPath+string(filepath.Separator)) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// IsSessionReadApproved checks if a file path falls under any session-approved directory.
+// The path is canonicalized (symlinks resolved) to prevent bypass.
+// Sensitive paths (e.g. ~/.ssh, ~/.aws) are never auto-approved.
+func IsSessionReadApproved(filePath string) bool {
+ canonical := canonicalizePath(filePath)
+ if isSensitivePath(canonical) {
+ return false
+ }
+ globalSessionApproval.mu.RLock()
+ defer globalSessionApproval.mu.RUnlock()
+ for approvedDir := range globalSessionApproval.approvedPaths {
+ if strings.HasPrefix(canonical, approvedDir) || canonical == strings.TrimSuffix(approvedDir, string(filepath.Separator)) {
+ return true
+ }
+ }
+ return false
+}
+
+// GetSessionApprovedPaths returns a copy of all currently approved paths.
+func GetSessionApprovedPaths() []string {
+ globalSessionApproval.mu.RLock()
+ defer globalSessionApproval.mu.RUnlock()
+ paths := make([]string, 0, len(globalSessionApproval.approvedPaths))
+ for p := range globalSessionApproval.approvedPaths {
+ paths = append(paths, p)
+ }
+ return paths
+}
+
+// ClearSessionApprovals removes all session-level read approvals.
+func ClearSessionApprovals() {
+ globalSessionApproval.mu.Lock()
+ defer globalSessionApproval.mu.Unlock()
+ globalSessionApproval.approvedPaths = make(map[string]bool)
+}
diff --git a/pkg/aiusechat/sessionhistory/handler.go b/pkg/aiusechat/sessionhistory/handler.go
new file mode 100644
index 0000000000..3db1b9c57c
--- /dev/null
+++ b/pkg/aiusechat/sessionhistory/handler.go
@@ -0,0 +1,44 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package sessionhistory
+
+import (
+ "encoding/json"
+ "net/http"
+ "os"
+)
+
+// HandleSessionHistory handles GET /wave/session-history?tabid=...
+func HandleSessionHistory(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ tabId := r.URL.Query().Get("tabid")
+ if tabId == "" {
+ writeEmptyResponse(w)
+ return
+ }
+
+ fileData, err := os.ReadFile(getHistoryFilePath(tabId))
+ if err != nil {
+ writeEmptyResponse(w)
+ return
+ }
+
+ var sessionLog SessionLog
+ if err := json.Unmarshal(fileData, &sessionLog); err != nil {
+ writeEmptyResponse(w)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(sessionLog)
+}
+
+func writeEmptyResponse(w http.ResponseWriter) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{"entries": nil})
+}
diff --git a/pkg/aiusechat/sessionhistory/registry.go b/pkg/aiusechat/sessionhistory/registry.go
new file mode 100644
index 0000000000..f51c098d1f
--- /dev/null
+++ b/pkg/aiusechat/sessionhistory/registry.go
@@ -0,0 +1,40 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package sessionhistory
+
+import "sync"
+
+// chatTabRegistry maps chatId → tabId so we can save history per-tab at shutdown.
+var (
+ chatTabMap = make(map[string]string)
+ chatTabMapMu sync.Mutex
+)
+
+// RegisterChatTab records which tab a chat belongs to.
+func RegisterChatTab(chatId string, tabId string) {
+ if chatId == "" || tabId == "" {
+ return
+ }
+ chatTabMapMu.Lock()
+ defer chatTabMapMu.Unlock()
+ chatTabMap[chatId] = tabId
+}
+
+// GetTabForChat returns the tabId for a chatId, or empty string if not found.
+func GetTabForChat(chatId string) string {
+ chatTabMapMu.Lock()
+ defer chatTabMapMu.Unlock()
+ return chatTabMap[chatId]
+}
+
+// GetAllMappings returns a copy of all chatId→tabId mappings.
+func GetAllMappings() map[string]string {
+ chatTabMapMu.Lock()
+ defer chatTabMapMu.Unlock()
+ result := make(map[string]string, len(chatTabMap))
+ for k, v := range chatTabMap {
+ result[k] = v
+ }
+ return result
+}
diff --git a/pkg/aiusechat/sessionhistory/saver.go b/pkg/aiusechat/sessionhistory/saver.go
new file mode 100644
index 0000000000..dc39d61677
--- /dev/null
+++ b/pkg/aiusechat/sessionhistory/saver.go
@@ -0,0 +1,70 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package sessionhistory
+
+import (
+ "log"
+ "strings"
+
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+)
+
+// ExtractSimpleMessages converts UIMessages to SimpleChatMessages for history storage.
+func ExtractSimpleMessages(messages []uctypes.UIMessage) []SimpleChatMessage {
+ var result []SimpleChatMessage
+ for _, msg := range messages {
+ role := msg.Role
+ if role != "user" && role != "assistant" {
+ continue
+ }
+
+ var text string
+ var toolUse string
+ for _, part := range msg.Parts {
+ if part.Type == uctypes.AIMessagePartTypeText {
+ if text == "" {
+ text = part.Text
+ }
+ } else if strings.HasPrefix(part.Type, "tool-") {
+ toolUse = strings.TrimPrefix(part.Type, "tool-")
+ }
+ }
+
+ if text == "" && toolUse == "" {
+ continue
+ }
+
+ result = append(result, SimpleChatMessage{
+ Role: role,
+ Text: text,
+ ToolUse: toolUse,
+ })
+ }
+ return result
+}
+
+// SaveChatAsHistory saves the given UIChat as session history for a tab.
+func SaveChatAsHistory(tabId string, uiChat *uctypes.UIChat) {
+ if uiChat == nil || len(uiChat.Messages) == 0 {
+ return
+ }
+ messages := ExtractSimpleMessages(uiChat.Messages)
+ if len(messages) == 0 {
+ return
+ }
+ if err := SaveSessionHistory(tabId, uiChat.Model, messages); err != nil {
+ log.Printf("[sessionhistory] error saving history for tab %s: %v\n", tabId, err)
+ }
+}
+
+// SaveAllCallback is set by usechat package to avoid circular imports.
+// It should convert all chats in chatstore to UIChat and save them.
+var SaveAllCallback func()
+
+// SaveAll calls the registered callback to save all active chats.
+func SaveAll() {
+ if SaveAllCallback != nil {
+ SaveAllCallback()
+ }
+}
diff --git a/pkg/aiusechat/sessionhistory/sessionhistory.go b/pkg/aiusechat/sessionhistory/sessionhistory.go
new file mode 100644
index 0000000000..8209f2c6e3
--- /dev/null
+++ b/pkg/aiusechat/sessionhistory/sessionhistory.go
@@ -0,0 +1,159 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package sessionhistory
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/woveterm/wove/pkg/wavebase"
+)
+
+const (
+ historyDirName = "session-history"
+ maxMessageLen = 500
+ maxMessagesPerLog = 30
+)
+
+// SessionEntry represents a single message in the session history.
+type SessionEntry struct {
+ Role string `json:"role"` // "user" or "assistant"
+ Text string `json:"text"` // truncated message content
+ ToolUse string `json:"tool,omitempty"` // tool name if this was a tool call
+}
+
+// SessionLog represents the saved history for one tab's last session.
+type SessionLog struct {
+ TabId string `json:"tabId"`
+ Timestamp string `json:"timestamp"`
+ Model string `json:"model,omitempty"`
+ Entries []SessionEntry `json:"entries"`
+}
+
+func getHistoryDir() string {
+ return filepath.Join(wavebase.GetWaveDataDir(), historyDirName)
+}
+
+func getHistoryFilePath(tabId string) string {
+ return filepath.Join(getHistoryDir(), tabId+".json")
+}
+
+// SaveSessionHistory saves a condensed version of the chat for the given tab.
+// Call this when a chat is cleared or when Wave shuts down.
+func SaveSessionHistory(tabId string, model string, messages []SimpleChatMessage) error {
+ if len(messages) == 0 {
+ return nil
+ }
+
+ dir := getHistoryDir()
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return fmt.Errorf("creating history dir: %w", err)
+ }
+
+ entries := make([]SessionEntry, 0, maxMessagesPerLog)
+ for _, msg := range messages {
+ if len(entries) >= maxMessagesPerLog {
+ break
+ }
+ entry := SessionEntry{
+ Role: msg.Role,
+ Text: truncate(msg.Text, maxMessageLen),
+ ToolUse: msg.ToolUse,
+ }
+ if entry.Text == "" && entry.ToolUse == "" {
+ continue
+ }
+ entries = append(entries, entry)
+ }
+
+ if len(entries) == 0 {
+ return nil
+ }
+
+ sessionLog := SessionLog{
+ TabId: tabId,
+ Timestamp: time.Now().Format(time.RFC3339),
+ Model: model,
+ Entries: entries,
+ }
+
+ data, err := json.MarshalIndent(sessionLog, "", " ")
+ if err != nil {
+ return fmt.Errorf("marshaling session history: %w", err)
+ }
+
+ if err := os.WriteFile(getHistoryFilePath(tabId), data, 0644); err != nil {
+ return fmt.Errorf("writing session history: %w", err)
+ }
+
+ log.Printf("[sessionhistory] saved %d entries for tab %s\n", len(entries), tabId[:8])
+ return nil
+}
+
+// LoadSessionHistory loads the previous session's history for the given tab.
+// Returns empty string if no history exists.
+func LoadSessionHistory(tabId string) string {
+ data, err := os.ReadFile(getHistoryFilePath(tabId))
+ if err != nil {
+ return ""
+ }
+
+ var sessionLog SessionLog
+ if err := json.Unmarshal(data, &sessionLog); err != nil {
+ return ""
+ }
+
+ if len(sessionLog.Entries) == 0 {
+ return ""
+ }
+
+ var sb strings.Builder
+ sb.WriteString("\n")
+ sb.WriteString(fmt.Sprintf("Last session: %s\n", sessionLog.Timestamp))
+ if sessionLog.Model != "" {
+ sb.WriteString(fmt.Sprintf("Model: %s\n", sessionLog.Model))
+ }
+ sb.WriteString("\n")
+
+ for _, entry := range sessionLog.Entries {
+ switch entry.Role {
+ case "user":
+ sb.WriteString(fmt.Sprintf("User: %s\n", entry.Text))
+ case "assistant":
+ if entry.ToolUse != "" {
+ sb.WriteString(fmt.Sprintf("AI [tool: %s]: %s\n", entry.ToolUse, entry.Text))
+ } else {
+ sb.WriteString(fmt.Sprintf("AI: %s\n", entry.Text))
+ }
+ }
+ }
+
+ sb.WriteString(" ")
+ return sb.String()
+}
+
+// DeleteSessionHistory removes the history file for the given tab.
+func DeleteSessionHistory(tabId string) {
+ os.Remove(getHistoryFilePath(tabId))
+}
+
+// SimpleChatMessage is a simplified message for saving to history.
+type SimpleChatMessage struct {
+ Role string
+ Text string
+ ToolUse string
+}
+
+func truncate(s string, maxLen int) string {
+ s = strings.TrimSpace(s)
+ if len(s) <= maxLen {
+ return s
+ }
+ return s[:maxLen] + "..."
+}
diff --git a/pkg/aiusechat/toolapproval.go b/pkg/aiusechat/toolapproval.go
index 4009c6dd71..0f7a8a2fdd 100644
--- a/pkg/aiusechat/toolapproval.go
+++ b/pkg/aiusechat/toolapproval.go
@@ -7,8 +7,8 @@ import (
"context"
"sync"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/web/sse"
)
type ApprovalRequest struct {
diff --git a/pkg/aiusechat/tools.go b/pkg/aiusechat/tools.go
index 550fec95cb..56874facd9 100644
--- a/pkg/aiusechat/tools.go
+++ b/pkg/aiusechat/tools.go
@@ -10,13 +10,13 @@ import (
"strings"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "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"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/blockcontroller"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wstore"
)
func makeTerminalBlockDesc(block *waveobj.Block) string {
@@ -102,8 +102,13 @@ func MakeBlockShortDesc(block *waveobj.Block) string {
}
return "file and directory preview widget"
case "web":
- if url, hasUrl := block.Meta["url"].(string); hasUrl && url != "" {
- return fmt.Sprintf("web browser widget pointing at %q", url)
+ url, _ := block.Meta["url"].(string)
+ title, _ := block.Meta["web:title"].(string)
+ if url != "" && title != "" {
+ return fmt.Sprintf("web browser: %q (%s)", title, url)
+ }
+ if url != "" {
+ return fmt.Sprintf("web browser: %s", url)
}
return "web browser widget"
case "waveai":
@@ -172,6 +177,9 @@ func GenerateTabStateAndTools(ctx context.Context, tabid string, widgetAccess bo
tools = append(tools, GetReadDirToolDefinition())
tools = append(tools, GetWriteTextFileToolDefinition())
tools = append(tools, GetEditTextFileToolDefinition())
+
+ // Consolidated utility tool (plans, session history, project instructions)
+ tools = append(tools, GetWaveUtilsToolDefinition(tabid))
tools = append(tools, GetDeleteTextFileToolDefinition())
viewTypes := make(map[string]bool)
for _, block := range blocks {
@@ -190,10 +198,14 @@ func GenerateTabStateAndTools(ctx context.Context, tabid string, widgetAccess bo
}
if viewTypes["term"] {
tools = append(tools, GetTermGetScrollbackToolDefinition(tabid))
+ tools = append(tools, GetTermRunCommandToolDefinition(tabid))
// tools = append(tools, GetTermCommandOutputToolDefinition(tabid))
}
if viewTypes["web"] {
tools = append(tools, GetWebNavigateToolDefinition(tabid))
+ tools = append(tools, GetWebReadTextToolDefinition(tabid))
+ tools = append(tools, GetWebReadHTMLToolDefinition(tabid))
+ tools = append(tools, GetWebSEOAuditToolDefinition(tabid))
}
}
return tabState, tools, nil
diff --git a/pkg/aiusechat/tools_builder.go b/pkg/aiusechat/tools_builder.go
index 14e71b8f45..e28096df16 100644
--- a/pkg/aiusechat/tools_builder.go
+++ b/pkg/aiusechat/tools_builder.go
@@ -10,15 +10,15 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/buildercontroller"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveappstore"
- "github.com/wavetermdev/waveterm/pkg/waveapputil"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/buildercontroller"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveappstore"
+ "github.com/woveterm/wove/pkg/waveapputil"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
)
const BuilderAppFileName = "app.go"
diff --git a/pkg/aiusechat/tools_history.go b/pkg/aiusechat/tools_history.go
new file mode 100644
index 0000000000..1f57dcfce8
--- /dev/null
+++ b/pkg/aiusechat/tools_history.go
@@ -0,0 +1,33 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiusechat
+
+import (
+ "github.com/woveterm/wove/pkg/aiusechat/sessionhistory"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+)
+
+func GetSessionHistoryToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "session_history",
+ DisplayName: "Previous Session History",
+ Description: "Read previous session's chat history (messages, tool calls, topics). Use when user references prior work.",
+ ShortDescription: "Read previous session history",
+ ToolLogName: "session:history",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{},
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ return "reading previous session history"
+ },
+ ToolTextCallback: func(input any) (string, error) {
+ history := sessionhistory.LoadSessionHistory(tabId)
+ if history == "" {
+ return "No previous session history found for this tab.", nil
+ }
+ return history, nil
+ },
+ }
+}
diff --git a/pkg/aiusechat/tools_mcp.go b/pkg/aiusechat/tools_mcp.go
new file mode 100644
index 0000000000..1612a838fb
--- /dev/null
+++ b/pkg/aiusechat/tools_mcp.go
@@ -0,0 +1,67 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiusechat
+
+import (
+ "context"
+ "log"
+
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/mcpclient"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wstore"
+)
+
+// generateMCPStateAndTools connects to MCP servers based on the CWD stored in chatOpts
+// and returns auto-context and tool definitions.
+func generateMCPStateAndTools(chatOpts uctypes.WaveChatOpts) (string, []uctypes.ToolDefinition, error) {
+ cwd := chatOpts.MCPCwd
+ if cwd == "" {
+ return "", nil, nil
+ }
+
+ if !mcpclient.HasMCPConfig(cwd) {
+ return "", nil, nil
+ }
+
+ manager := mcpclient.GetManager()
+
+ // Get auto-context (application-info, database-schema summary)
+ mcpState, err := manager.GetAutoContext(cwd)
+ if err != nil {
+ log.Printf("[mcp] warning: failed to get auto-context: %v\n", err)
+ mcpState = ""
+ }
+
+ // Get tool definitions (all MCP tools wrapped as Wave ToolDefinitions)
+ mcpTools, err := manager.GetToolDefinitions(cwd)
+ if err != nil {
+ log.Printf("[mcp] warning: failed to get tool definitions: %v\n", err)
+ mcpTools = nil
+ }
+
+ return mcpState, mcpTools, nil
+}
+
+// getTerminalCwd extracts the CWD from the first terminal block in the given tab.
+func getTerminalCwd(ctx context.Context, tabId string) string {
+ tabObj, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId)
+ if err != nil {
+ return ""
+ }
+ for _, blockId := range tabObj.BlockIds {
+ block, err := wstore.DBGet[*waveobj.Block](ctx, blockId)
+ if err != nil || block == nil || block.Meta == nil {
+ continue
+ }
+ viewType, _ := block.Meta["view"].(string)
+ if viewType != "term" {
+ continue
+ }
+ if cwd, ok := block.Meta["cmd:cwd"].(string); ok && cwd != "" {
+ return cwd
+ }
+ }
+ return ""
+}
diff --git a/pkg/aiusechat/tools_plan.go b/pkg/aiusechat/tools_plan.go
new file mode 100644
index 0000000000..6beb5b3099
--- /dev/null
+++ b/pkg/aiusechat/tools_plan.go
@@ -0,0 +1,167 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiusechat
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/woveterm/wove/pkg/aiusechat/aiplan"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+)
+
+func GetPlanCreateToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "plan_create",
+ DisplayName: "Create Plan",
+ Description: "Create a multi-step plan for repeating operations across items. Each step runs independently. Start executing step 1 immediately after creation.",
+ ShortDescription: "Create multi-step plan",
+ ToolLogName: "plan:create",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "name": map[string]any{
+ "type": "string",
+ "description": "Short name for the plan (e.g., 'SEO Audit mits.pl')",
+ },
+ "description": map[string]any{
+ "type": "string",
+ "description": "What to do for each step (e.g., 'Run SEO audit and report issues')",
+ },
+ "steps": map[string]any{
+ "type": "array",
+ "description": "List of step labels (e.g., URLs to audit, files to process)",
+ "items": map[string]any{
+ "type": "string",
+ },
+ },
+ },
+ "required": []string{"name", "steps"},
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ inputMap, _ := input.(map[string]any)
+ name, _ := inputMap["name"].(string)
+ steps, _ := inputMap["steps"].([]any)
+ return fmt.Sprintf("creating plan %q with %d steps", name, len(steps))
+ },
+ ToolTextCallback: func(input any) (string, error) {
+ inputMap, ok := input.(map[string]any)
+ if !ok {
+ return "", fmt.Errorf("invalid input format")
+ }
+
+ name, _ := inputMap["name"].(string)
+ if name == "" {
+ return "", fmt.Errorf("name is required")
+ }
+
+ description, _ := inputMap["description"].(string)
+
+ stepsRaw, _ := inputMap["steps"].([]any)
+ if len(stepsRaw) == 0 {
+ return "", fmt.Errorf("at least one step is required")
+ }
+
+ stepLabels := make([]string, len(stepsRaw))
+ for i, s := range stepsRaw {
+ stepLabels[i] = fmt.Sprintf("%v", s)
+ }
+
+ plan, err := aiplan.CreatePlan(tabId, name, description, stepLabels)
+ if err != nil {
+ return "", err
+ }
+
+ return aiplan.FormatPlanStatus(plan), nil
+ },
+ }
+}
+
+func GetPlanStatusToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "plan_status",
+ DisplayName: "Plan Status",
+ Description: "Get active plan status: done/pending steps and what to do next.",
+ ShortDescription: "Check plan status",
+ ToolLogName: "plan:status",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{},
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ return "checking plan status"
+ },
+ ToolTextCallback: func(input any) (string, error) {
+ plan := aiplan.GetPlan(tabId)
+ if plan == nil {
+ return "No active plan for this tab.", nil
+ }
+ return aiplan.FormatPlanStatus(plan), nil
+ },
+ }
+}
+
+func GetPlanUpdateToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "plan_update",
+ DisplayName: "Update Plan Step",
+ Description: "Mark a plan step as done/failed/skipped with result summary. Continue with next pending step.",
+ ShortDescription: "Update plan step",
+ ToolLogName: "plan:update",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "step_id": map[string]any{
+ "type": "integer",
+ "description": "Step number to update",
+ },
+ "status": map[string]any{
+ "type": "string",
+ "enum": []string{"done", "failed", "skipped"},
+ "description": "New status for the step",
+ },
+ "result": map[string]any{
+ "type": "string",
+ "description": "Brief summary of what was found/done in this step",
+ },
+ },
+ "required": []string{"step_id", "status"},
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ inputMap, _ := input.(map[string]any)
+ stepId, _ := inputMap["step_id"].(float64)
+ status, _ := inputMap["status"].(string)
+ return fmt.Sprintf("marking step #%d as %s", int(stepId), status)
+ },
+ ToolTextCallback: func(input any) (string, error) {
+ inputBytes, err := json.Marshal(input)
+ if err != nil {
+ return "", fmt.Errorf("invalid input: %w", err)
+ }
+
+ var params struct {
+ StepId int `json:"step_id"`
+ Status string `json:"status"`
+ Result string `json:"result"`
+ }
+ if err := json.Unmarshal(inputBytes, ¶ms); err != nil {
+ return "", fmt.Errorf("parsing input: %w", err)
+ }
+
+ if params.StepId == 0 {
+ return "", fmt.Errorf("step_id is required")
+ }
+ if params.Status == "" {
+ params.Status = aiplan.StatusDone
+ }
+
+ plan, err := aiplan.UpdateStep(tabId, params.StepId, params.Status, params.Result, "")
+ if err != nil {
+ return "", err
+ }
+
+ return aiplan.FormatPlanStatus(plan), nil
+ },
+ }
+}
diff --git a/pkg/aiusechat/tools_projectctx.go b/pkg/aiusechat/tools_projectctx.go
new file mode 100644
index 0000000000..e0f1f2c5ea
--- /dev/null
+++ b/pkg/aiusechat/tools_projectctx.go
@@ -0,0 +1,156 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiusechat
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/woveterm/wove/pkg/aiusechat/projectctx"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+)
+
+func GetProjectInstructionsToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "project_instructions",
+ DisplayName: "Project Instructions",
+ Description: "Read project instructions (WAVE.md, CLAUDE.md, .cursorrules). No params = table of contents. With sections=[...] = full content. Optional file_ext filter.",
+ ShortDescription: "Read project coding instructions",
+ ToolLogName: "project:instructions",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "sections": map[string]any{
+ "type": "array",
+ "description": "List of section headings to retrieve (e.g., [\"Database\", \"Controllers & Validation\"]). Omit to get table of contents.",
+ "items": map[string]any{
+ "type": "string",
+ },
+ },
+ "file_ext": map[string]any{
+ "type": "string",
+ "description": "Optional file extension to filter sections by technology (e.g., 'php', 'vue', 'ts').",
+ },
+ },
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ inputMap, _ := input.(map[string]any)
+ sections, _ := inputMap["sections"].([]any)
+ if len(sections) > 0 {
+ return fmt.Sprintf("reading %d project instruction sections", len(sections))
+ }
+ return "listing project instruction sections"
+ },
+ ToolTextCallback: makeProjectInstructionsCallback(tabId),
+ }
+}
+
+func makeProjectInstructionsCallback(tabId string) func(any) (string, error) {
+ return func(input any) (string, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ cwd := getTerminalCwd(ctx, tabId)
+ if cwd == "" {
+ return "No terminal found in this tab.", nil
+ }
+
+ files := projectctx.FindAllInstructionsFiles(cwd)
+ if len(files) == 0 {
+ return "No project instructions file found. Looked for: WAVE.md, CLAUDE.md, GEMINI.md, AGENTS.md, .cursorrules", nil
+ }
+
+ // Parse all files
+ var allInstructions []*projectctx.ProjectInstructions
+ for _, filePath := range files {
+ pi, err := projectctx.ParseInstructions(filePath)
+ if err != nil {
+ continue
+ }
+ allInstructions = append(allInstructions, pi)
+ }
+
+ if len(allInstructions) == 0 {
+ return "Instructions files found but could not be parsed.", nil
+ }
+
+ inputMap, _ := input.(map[string]any)
+ fileExt, _ := inputMap["file_ext"].(string)
+ sectionsRaw, _ := inputMap["sections"].([]any)
+
+ // Mode 1: Table of contents (no sections requested)
+ if len(sectionsRaw) == 0 {
+ return formatTableOfContents(allInstructions, fileExt), nil
+ }
+
+ // Mode 2: Return requested sections
+ requestedSections := make([]string, len(sectionsRaw))
+ for i, s := range sectionsRaw {
+ requestedSections[i] = fmt.Sprintf("%v", s)
+ }
+ return formatRequestedSections(allInstructions, requestedSections, fileExt), nil
+ }
+}
+
+func formatTableOfContents(instructions []*projectctx.ProjectInstructions, fileExt string) string {
+ var sb strings.Builder
+ sb.WriteString("Project instruction files found:\n\n")
+
+ for _, pi := range instructions {
+ sb.WriteString(fmt.Sprintf("📄 **%s** (%d sections, %d bytes)\n", pi.FileName, len(pi.Sections), pi.RawSize))
+
+ for _, section := range pi.Sections {
+ if fileExt != "" && !projectctx.SectionMatchesExt(section, fileExt) {
+ continue
+ }
+ tags := ""
+ if len(section.Tags) > 0 {
+ tags = " [" + strings.Join(section.Tags, ", ") + "]"
+ }
+ lines := strings.Count(section.Content, "\n") + 1
+ sb.WriteString(fmt.Sprintf(" - %s (%d lines)%s\n", section.Heading, lines, tags))
+ }
+ sb.WriteString("\n")
+ }
+
+ sb.WriteString("Call project_instructions with sections=[\"Section Name\", ...] to read specific sections.")
+ return sb.String()
+}
+
+func formatRequestedSections(instructions []*projectctx.ProjectInstructions, requested []string, fileExt string) string {
+ var sb strings.Builder
+
+ requestedLower := make(map[string]bool)
+ for _, r := range requested {
+ requestedLower[strings.ToLower(r)] = true
+ }
+
+ found := 0
+ for _, pi := range instructions {
+ for _, section := range pi.Sections {
+ if !requestedLower[strings.ToLower(section.Heading)] {
+ continue
+ }
+ if fileExt != "" && !projectctx.SectionMatchesExt(section, fileExt) {
+ continue
+ }
+ sb.WriteString(fmt.Sprintf("## %s (from %s)\n", section.Heading, pi.FileName))
+ sb.WriteString(section.Content)
+ sb.WriteString("\n\n")
+ found++
+ }
+ }
+
+ if found == 0 {
+ return fmt.Sprintf("No sections found matching: %s", strings.Join(requested, ", "))
+ }
+
+ result := sb.String()
+ const maxLen = 12000
+ if len(result) > maxLen {
+ result = result[:maxLen] + "\n... [truncated]"
+ }
+ return result
+}
diff --git a/pkg/aiusechat/tools_readdir.go b/pkg/aiusechat/tools_readdir.go
index da7d568f84..6991df0828 100644
--- a/pkg/aiusechat/tools_readdir.go
+++ b/pkg/aiusechat/tools_readdir.go
@@ -8,10 +8,10 @@ import (
"os"
"path/filepath"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const ReadDirDefaultMaxEntries = 500
@@ -124,7 +124,7 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "read_dir",
DisplayName: "Read Directory",
- Description: "Read a directory from the filesystem and list its contents. Returns information about files and subdirectories including names, types, sizes, permissions, and modification times.",
+ Description: "List directory contents: names, types, sizes, permissions, modification times.",
ToolLogName: "gen:readdir",
Strict: false,
InputSchema: map[string]any{
@@ -166,6 +166,17 @@ func GetReadDirToolDefinition() uctypes.ToolDefinition {
},
ToolAnyCallback: readDirCallback,
ToolApproval: func(input any) string {
+ parsed, err := parseReadDirInput(input)
+ if err != nil {
+ return uctypes.ApprovalNeedsApproval
+ }
+ expandedPath, err := wavebase.ExpandHomeDir(parsed.Path)
+ if err != nil {
+ return uctypes.ApprovalNeedsApproval
+ }
+ if IsSessionReadApproved(expandedPath) {
+ return uctypes.ApprovalAutoApproved
+ }
return uctypes.ApprovalNeedsApproval
},
ToolVerifyInput: verifyReadDirInput,
diff --git a/pkg/aiusechat/tools_readdir_test.go b/pkg/aiusechat/tools_readdir_test.go
index 7d91f7dfca..49ff0cfe25 100644
--- a/pkg/aiusechat/tools_readdir_test.go
+++ b/pkg/aiusechat/tools_readdir_test.go
@@ -10,8 +10,8 @@ import (
"strings"
"testing"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/fileutil"
)
func TestReadDirCallback(t *testing.T) {
diff --git a/pkg/aiusechat/tools_readfile.go b/pkg/aiusechat/tools_readfile.go
index eecc2385b6..38c641f1e8 100644
--- a/pkg/aiusechat/tools_readfile.go
+++ b/pkg/aiusechat/tools_readfile.go
@@ -11,10 +11,10 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/util/readutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/util/readutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const ReadFileDefaultLineCount = 100
@@ -328,7 +328,7 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "read_text_file",
DisplayName: "Read Text File",
- Description: "Read a text file from the filesystem. Can read specific line ranges or from the end. Detects and rejects binary files.",
+ Description: "Read text file. Supports line ranges and tail. Rejects binary.",
ToolLogName: "gen:readfile",
Strict: false,
InputSchema: map[string]any{
@@ -403,6 +403,17 @@ func GetReadTextFileToolDefinition() uctypes.ToolDefinition {
},
ToolAnyCallback: readTextFileCallback,
ToolApproval: func(input any) string {
+ parsed, err := parseReadTextFileInput(input)
+ if err != nil {
+ return uctypes.ApprovalNeedsApproval
+ }
+ expandedPath, err := wavebase.ExpandHomeDir(parsed.Filename)
+ if err != nil {
+ return uctypes.ApprovalNeedsApproval
+ }
+ if IsSessionReadApproved(expandedPath) {
+ return uctypes.ApprovalAutoApproved
+ }
return uctypes.ApprovalNeedsApproval
},
ToolVerifyInput: verifyReadTextFileInput,
diff --git a/pkg/aiusechat/tools_screenshot.go b/pkg/aiusechat/tools_screenshot.go
index 9df5a18f0e..27b78a9793 100644
--- a/pkg/aiusechat/tools_screenshot.go
+++ b/pkg/aiusechat/tools_screenshot.go
@@ -8,11 +8,11 @@ import (
"fmt"
"time"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "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/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
func makeTabCaptureBlockScreenshot(tabId string) func(any) (string, error) {
@@ -53,7 +53,7 @@ func GetCaptureScreenshotToolDefinition(tabId string) uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "capture_screenshot",
DisplayName: "Capture Screenshot",
- Description: "Capture a screenshot of a widget and return it as an image",
+ Description: "Screenshot a widget, returns image.",
ToolLogName: "gen:screenshot",
Strict: true,
InputSchema: map[string]any{
diff --git a/pkg/aiusechat/tools_term.go b/pkg/aiusechat/tools_term.go
index 7d0ad90507..6359bbb37b 100644
--- a/pkg/aiusechat/tools_term.go
+++ b/pkg/aiusechat/tools_term.go
@@ -10,13 +10,13 @@ import (
"strings"
"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"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wstore"
)
type TermGetScrollbackToolInput struct {
@@ -94,9 +94,12 @@ func getTermScrollbackOutput(tabId string, widgetId string, rpcData wshrpc.Comma
result, err := wshclient.TermGetScrollbackLinesCommand(
rpcClient,
rpcData,
- &wshrpc.RpcOpts{Route: wshutil.MakeFeBlockRouteId(fullBlockId)},
+ &wshrpc.RpcOpts{Route: wshutil.MakeFeBlockRouteId(fullBlockId), Timeout: 5000},
)
if err != nil {
+ if strings.Contains(err.Error(), "no route") {
+ return nil, fmt.Errorf("terminal widget %s is not active - make sure it is visible on screen. Try using term_run_command instead", widgetId)
+ }
return nil, err
}
@@ -155,7 +158,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). Also returns last command and exit code if shell integration is enabled.",
+ Description: "Get terminal scrollback text. Index 0 = newest line. Includes last command and exit code.",
ToolLogName: "term:getscrollback",
InputSchema: map[string]any{
"type": "object",
@@ -245,7 +248,7 @@ func GetTermCommandOutputToolDefinition(tabId string) uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "term_command_output",
DisplayName: "Get Last Command Output",
- Description: "Retrieve output from the most recent command in a terminal widget. Requires shell integration to be enabled. Returns the command text, exit code, and up to 1000 lines of output.",
+ Description: "Get last command's output, exit code, and command text. Max 1000 lines. Requires shell integration.",
ToolLogName: "term:commandoutput",
InputSchema: map[string]any{
"type": "object",
diff --git a/pkg/aiusechat/tools_termrun.go b/pkg/aiusechat/tools_termrun.go
new file mode 100644
index 0000000000..cf58d29203
--- /dev/null
+++ b/pkg/aiusechat/tools_termrun.go
@@ -0,0 +1,239 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiusechat
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/blockcontroller"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wstore"
+)
+
+const (
+ TermRunCommandTimeout = 60 * time.Second
+ TermRunCommandPollPeriod = 250 * time.Millisecond
+ TermRunMaxOutputLines = 1000
+)
+
+type TermRunCommandInput struct {
+ WidgetId string `json:"widget_id"`
+ Command string `json:"command"`
+}
+
+type TermRunCommandOutput struct {
+ Command string `json:"command"`
+ ExitCode *int `json:"exitcode,omitempty"`
+ Output string `json:"output"`
+ TimedOut bool `json:"timedout,omitempty"`
+}
+
+func parseTermRunCommandInput(input any) (*TermRunCommandInput, error) {
+ result := &TermRunCommandInput{}
+
+ if input == nil {
+ return nil, fmt.Errorf("input is required")
+ }
+
+ if err := utilfn.ReUnmarshal(result, input); err != nil {
+ return nil, fmt.Errorf("invalid input format: %w", err)
+ }
+
+ if result.WidgetId == "" {
+ return nil, fmt.Errorf("widget_id is required")
+ }
+
+ if result.Command == "" {
+ return nil, fmt.Errorf("command is required")
+ }
+
+ return result, nil
+}
+
+func sendCommandToTerminal(blockId string, command string) error {
+ // Send the command text followed by a newline (Enter key)
+ inputData := []byte(command + "\n")
+ inputUnion := &blockcontroller.BlockInputUnion{
+ InputData: inputData,
+ }
+ return blockcontroller.SendInput(blockId, inputUnion)
+}
+
+func waitForCommandCompletion(ctx context.Context, blockORef waveobj.ORef) (bool, error) {
+ ticker := time.NewTicker(TermRunCommandPollPeriod)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return false, ctx.Err()
+ case <-ticker.C:
+ rtInfo := wstore.GetRTInfo(blockORef)
+ if rtInfo == nil {
+ return false, fmt.Errorf("terminal runtime info not available")
+ }
+ if !rtInfo.ShellIntegration {
+ return false, fmt.Errorf("shell integration is not enabled for this terminal")
+ }
+ if rtInfo.ShellState == "ready" {
+ return true, nil
+ }
+ // still running, continue polling
+ }
+ }
+}
+
+func GetTermRunCommandToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "term_run_command",
+ DisplayName: "Run Terminal Command",
+ Description: "Run a command in terminal and return output. For CLI tools (git, npm, artisan, etc.). Requires shell integration.",
+ ToolLogName: "term:runcommand",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "widget_id": map[string]any{
+ "type": "string",
+ "description": "8-character widget ID of the terminal widget",
+ },
+ "command": map[string]any{
+ "type": "string",
+ "description": "The command to execute in the terminal (e.g., 'php artisan migrate:status', 'composer validate', 'ls -la')",
+ },
+ },
+ "required": []string{"widget_id", "command"},
+ "additionalProperties": false,
+ },
+ ToolCallDesc: func(input any, output any, toolUseData *uctypes.UIMessageDataToolUse) string {
+ parsed, err := parseTermRunCommandInput(input)
+ if err != nil {
+ return fmt.Sprintf("error parsing input: %v", err)
+ }
+ cmdStr := parsed.Command
+ if len(cmdStr) > 60 {
+ cmdStr = cmdStr[:57] + "..."
+ }
+ if output != nil {
+ return fmt.Sprintf("ran `%s` in %s", cmdStr, parsed.WidgetId)
+ }
+ return fmt.Sprintf("running `%s` in %s", cmdStr, parsed.WidgetId)
+ },
+ ToolApproval: func(input any) string {
+ return uctypes.ApprovalNeedsApproval
+ },
+ ToolVerifyInput: func(input any, toolUseData *uctypes.UIMessageDataToolUse) error {
+ parsed, err := parseTermRunCommandInput(input)
+ if err != nil {
+ return err
+ }
+
+ ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancelFn()
+
+ fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, tabId, parsed.WidgetId)
+ if err != nil {
+ return fmt.Errorf("terminal widget not found: %w", err)
+ }
+
+ blockORef := waveobj.MakeORef(waveobj.OType_Block, fullBlockId)
+ rtInfo := wstore.GetRTInfo(blockORef)
+ if rtInfo == nil {
+ return fmt.Errorf("terminal runtime info not available")
+ }
+ if !rtInfo.ShellIntegration {
+ return fmt.Errorf("shell integration is not enabled for this terminal — it is required to track command execution")
+ }
+ if rtInfo.ShellState == "running-command" {
+ return fmt.Errorf("terminal is currently running another command, wait for it to finish first")
+ }
+
+ return nil
+ },
+ ToolAnyCallback: func(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) {
+ parsed, err := parseTermRunCommandInput(input)
+ if err != nil {
+ return nil, err
+ }
+
+ ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancelFn()
+
+ fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, tabId, parsed.WidgetId)
+ if err != nil {
+ return nil, fmt.Errorf("terminal widget not found: %w", err)
+ }
+
+ blockORef := waveobj.MakeORef(waveobj.OType_Block, fullBlockId)
+ rtInfo := wstore.GetRTInfo(blockORef)
+ if rtInfo == nil {
+ return nil, fmt.Errorf("terminal runtime info not available")
+ }
+ if !rtInfo.ShellIntegration {
+ return nil, fmt.Errorf("shell integration is not enabled for this terminal")
+ }
+ if rtInfo.ShellState == "running-command" {
+ return nil, fmt.Errorf("terminal is currently running another command")
+ }
+
+ // Send the command to the terminal
+ err = sendCommandToTerminal(fullBlockId, parsed.Command)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send command to terminal: %w", err)
+ }
+
+ // Wait briefly for the command to start
+ time.Sleep(100 * time.Millisecond)
+
+ // Wait for the command to complete with a timeout
+ waitCtx, waitCancel := context.WithTimeout(context.Background(), TermRunCommandTimeout)
+ defer waitCancel()
+
+ completed, err := waitForCommandCompletion(waitCtx, blockORef)
+
+ // Read the output regardless of whether it completed or timed out
+ rpcClient := wshclient.GetBareRpcClient()
+ scrollbackResult, scrollErr := wshclient.TermGetScrollbackLinesCommand(
+ rpcClient,
+ wshrpc.CommandTermGetScrollbackLinesData{
+ LastCommand: true,
+ },
+ &wshrpc.RpcOpts{Route: wshutil.MakeFeBlockRouteId(fullBlockId)},
+ )
+
+ output := &TermRunCommandOutput{
+ Command: parsed.Command,
+ }
+
+ if err != nil && !completed {
+ output.TimedOut = true
+ }
+
+ // Get exit code from RTInfo
+ latestRtInfo := wstore.GetRTInfo(blockORef)
+ if latestRtInfo != nil && latestRtInfo.ShellState == "ready" {
+ exitCode := latestRtInfo.ShellLastCmdExitCode
+ output.ExitCode = &exitCode
+ }
+
+ if scrollErr == nil && scrollbackResult != nil {
+ lines := scrollbackResult.Lines
+ if len(lines) > TermRunMaxOutputLines {
+ lines = lines[len(lines)-TermRunMaxOutputLines:]
+ }
+ output.Output = strings.Join(lines, "\n")
+ }
+
+ return output, nil
+ },
+ }
+}
diff --git a/pkg/aiusechat/tools_tsunami.go b/pkg/aiusechat/tools_tsunami.go
index 1aeebd3fe1..9608461e6c 100644
--- a/pkg/aiusechat/tools_tsunami.go
+++ b/pkg/aiusechat/tools_tsunami.go
@@ -11,12 +11,12 @@ import (
"strings"
"time"
- "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/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/blockcontroller"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wstore"
)
func getTsunamiShortDesc(rtInfo *waveobj.ObjRTInfo) string {
diff --git a/pkg/aiusechat/tools_utils.go b/pkg/aiusechat/tools_utils.go
new file mode 100644
index 0000000000..fb564bafba
--- /dev/null
+++ b/pkg/aiusechat/tools_utils.go
@@ -0,0 +1,194 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package aiusechat
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/woveterm/wove/pkg/aiusechat/aiplan"
+ "github.com/woveterm/wove/pkg/aiusechat/projectctx"
+ "github.com/woveterm/wove/pkg/aiusechat/sessionhistory"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+)
+
+// GetWaveUtilsToolDefinition returns a consolidated tool for less frequently used operations.
+// This saves ~800 tokens compared to having 5 separate tool definitions.
+func GetWaveUtilsToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "wave_utils",
+ DisplayName: "Wave Utilities",
+ Description: "Utility tool. Actions: " +
+ "session_history - previous session; " +
+ "project_instructions - read CLAUDE.md/WAVE.md (params: sections, file_ext); " +
+ "plan_create - multi-step plan (params: name, steps[{label,details}] - include concrete requirements, file paths, conventions in details); " +
+ "plan_update - mark step done/failed (params: step_id, status, result); " +
+ "plan_status - check progress",
+ ShortDescription: "Session history, project instructions, plans",
+ ToolLogName: "wave:utils",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "action": map[string]any{
+ "type": "string",
+ "enum": []string{"session_history", "project_instructions", "plan_create", "plan_update", "plan_status"},
+ "description": "Which action to run",
+ },
+ "sections": map[string]any{
+ "type": "array",
+ "description": "For project_instructions: section headings to retrieve",
+ "items": map[string]any{"type": "string"},
+ },
+ "file_ext": map[string]any{
+ "type": "string",
+ "description": "For project_instructions: filter by file extension",
+ },
+ "name": map[string]any{
+ "type": "string",
+ "description": "For plan_create: plan name",
+ },
+ "description": map[string]any{
+ "type": "string",
+ "description": "For plan_create: what to do per step",
+ },
+ "steps": map[string]any{
+ "type": "array",
+ "description": "For plan_create: step labels",
+ "items": map[string]any{"type": "string"},
+ },
+ "step_id": map[string]any{
+ "type": "integer",
+ "description": "For plan_update: step number",
+ },
+ "status": map[string]any{
+ "type": "string",
+ "description": "For plan_update: done/failed/skipped",
+ },
+ "result": map[string]any{
+ "type": "string",
+ "description": "For plan_update: result summary",
+ },
+ },
+ "required": []string{"action"},
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ inputMap, _ := input.(map[string]any)
+ action, _ := inputMap["action"].(string)
+ return fmt.Sprintf("wave_utils: %s", action)
+ },
+ ToolTextCallback: func(input any) (string, error) {
+ var params map[string]any
+ data, _ := json.Marshal(input)
+ json.Unmarshal(data, ¶ms)
+
+ action, _ := params["action"].(string)
+
+ switch action {
+ case "session_history":
+ history := sessionhistory.LoadSessionHistory(tabId)
+ if history == "" {
+ return "No previous session history found.", nil
+ }
+ return history, nil
+
+ case "project_instructions":
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ cwd := getTerminalCwd(ctx, tabId)
+ if cwd == "" {
+ return "No terminal found.", nil
+ }
+ files := projectctx.FindAllInstructionsFiles(cwd)
+ if len(files) == 0 {
+ return "No project instructions file found.", nil
+ }
+ var allInstructions []*projectctx.ProjectInstructions
+ for _, f := range files {
+ pi, err := projectctx.ParseInstructions(f)
+ if err == nil {
+ allInstructions = append(allInstructions, pi)
+ }
+ }
+ sectionsRaw, _ := params["sections"].([]any)
+ fileExt, _ := params["file_ext"].(string)
+ if len(sectionsRaw) == 0 {
+ return formatTableOfContents(allInstructions, fileExt), nil
+ }
+ requested := make([]string, len(sectionsRaw))
+ for i, s := range sectionsRaw {
+ requested[i] = fmt.Sprintf("%v", s)
+ }
+ result := formatRequestedSections(allInstructions, requested, fileExt)
+ // Fallback: if no sections matched, return table of contents so AI can retry
+ if strings.HasPrefix(result, "No sections found") {
+ return result + "\n\n" + formatTableOfContents(allInstructions, fileExt), nil
+ }
+ return result, nil
+
+ case "plan_create":
+ name, _ := params["name"].(string)
+ desc, _ := params["description"].(string)
+ stepsRaw, _ := params["steps"].([]any)
+ if name == "" || len(stepsRaw) == 0 {
+ return "", fmt.Errorf("plan_create requires name and steps")
+ }
+ var labels []string
+ var details []string
+ for _, s := range stepsRaw {
+ switch v := s.(type) {
+ case string:
+ labels = append(labels, v)
+ details = append(details, "")
+ case map[string]any:
+ l, _ := v["label"].(string)
+ d, _ := v["details"].(string)
+ if l == "" {
+ l = fmt.Sprintf("%v", s)
+ }
+ labels = append(labels, l)
+ details = append(details, d)
+ default:
+ labels = append(labels, fmt.Sprintf("%v", s))
+ details = append(details, "")
+ }
+ }
+ plan, err := aiplan.CreatePlanWithDetails(tabId, name, desc, labels, details)
+ if err != nil {
+ return "", err
+ }
+ return aiplan.FormatPlanStatus(plan), nil
+
+ case "plan_update":
+ stepIdRaw, ok := params["step_id"].(float64)
+ if !ok || stepIdRaw == 0 {
+ return "", fmt.Errorf("step_id required (must be a number)")
+ }
+ stepId := int(stepIdRaw)
+ status, _ := params["status"].(string)
+ result, _ := params["result"].(string)
+ if status == "" {
+ status = aiplan.StatusDone
+ }
+ plan, err := aiplan.UpdateStep(tabId, stepId, status, result, "")
+ if err != nil {
+ return "", err
+ }
+ return aiplan.FormatPlanStatus(plan), nil
+
+ case "plan_status":
+ plan := aiplan.GetPlan(tabId)
+ if plan == nil {
+ return "No active plan.", nil
+ }
+ return aiplan.FormatPlanStatus(plan), nil
+
+ default:
+ return "", fmt.Errorf("unknown action: %s", action)
+ }
+ },
+ }
+}
diff --git a/pkg/aiusechat/tools_web.go b/pkg/aiusechat/tools_web.go
index 49b0cc33f4..bed48ffec2 100644
--- a/pkg/aiusechat/tools_web.go
+++ b/pkg/aiusechat/tools_web.go
@@ -7,12 +7,16 @@ import (
"context"
"encoding/json"
"fmt"
+ "strings"
"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/wstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wstore"
)
type WebNavigateToolInput struct {
@@ -52,7 +56,7 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "web_navigate",
DisplayName: "Navigate Web Widget",
- Description: "Navigate a web browser widget to a new URL",
+ Description: "Navigate web widget to a URL.",
ToolLogName: "web:navigate",
Strict: true,
InputSchema: map[string]any{
@@ -106,3 +110,250 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition {
},
}
}
+
+// webSelectorInput holds parsed input for web selector tools.
+type webSelectorInput struct {
+ WidgetId string
+ Selector string
+}
+
+func parseWebSelectorInput(input any) (*webSelectorInput, error) {
+ inputMap, ok := input.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("invalid input format")
+ }
+ widgetId, ok := inputMap["widget_id"].(string)
+ if !ok || widgetId == "" {
+ return nil, fmt.Errorf("missing or invalid widget_id parameter")
+ }
+ selector, _ := inputMap["selector"].(string)
+ if selector == "" {
+ selector = "body"
+ }
+ return &webSelectorInput{WidgetId: widgetId, Selector: selector}, nil
+}
+
+// webReadContent resolves a web widget, reloads it, and fetches content via CSS selector.
+func webReadContent(tabId string, input *webSelectorInput, opts *wshrpc.WebSelectorOpts) (string, error) {
+ ctx, cancelFn := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancelFn()
+
+ fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, tabId, input.WidgetId)
+ if err != nil {
+ return "", fmt.Errorf("resolving block: %w", err)
+ }
+
+ rpcClient := wshclient.GetBareRpcClient()
+ blockInfo, err := wshclient.BlockInfoCommand(rpcClient, fullBlockId, nil)
+ if err != nil {
+ return "", fmt.Errorf("getting block info: %w", err)
+ }
+
+ // Reload the page before reading to ensure fresh content
+ reloadData := wshrpc.CommandWebSelectorData{
+ WorkspaceId: blockInfo.WorkspaceId,
+ BlockId: fullBlockId,
+ TabId: blockInfo.TabId,
+ Selector: "body",
+ Opts: &wshrpc.WebSelectorOpts{Reload: true},
+ }
+ _, _ = wshclient.WebSelectorCommand(rpcClient, reloadData, &wshrpc.RpcOpts{
+ Route: wshutil.ElectronRoute,
+ Timeout: 15000,
+ })
+
+ // Fetch content with the requested options
+ data := wshrpc.CommandWebSelectorData{
+ WorkspaceId: blockInfo.WorkspaceId,
+ BlockId: fullBlockId,
+ TabId: blockInfo.TabId,
+ Selector: input.Selector,
+ Opts: opts,
+ }
+ results, err := wshclient.WebSelectorCommand(rpcClient, data, &wshrpc.RpcOpts{
+ Route: wshutil.ElectronRoute,
+ Timeout: 10000,
+ })
+ if err != nil {
+ return "", fmt.Errorf("reading web content: %w", err)
+ }
+ if len(results) == 0 {
+ return "", fmt.Errorf("no elements matched selector %q", input.Selector)
+ }
+
+ text := strings.Join(results, "\n")
+ const maxLen = 50000
+ if len(text) > maxLen {
+ text = text[:maxLen] + "\n... [truncated]"
+ }
+ return text, nil
+}
+
+func webToolCallDesc(toolAction string) func(any, any, *uctypes.UIMessageDataToolUse) string {
+ return func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ parsed, err := parseWebSelectorInput(input)
+ if err != nil {
+ return fmt.Sprintf("error: %v", err)
+ }
+ return fmt.Sprintf("%s from web widget %s (selector: %s)", toolAction, parsed.WidgetId, parsed.Selector)
+ }
+}
+
+var webSelectorSchema = map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "widget_id": map[string]any{
+ "type": "string",
+ "description": "8-character widget ID of the web browser widget",
+ },
+ "selector": map[string]any{
+ "type": "string",
+ "description": "CSS selector to target elements (e.g. 'body', 'main', 'article', '.content', '#main-text'). Defaults to 'body'.",
+ },
+ },
+ "required": []string{"widget_id"},
+}
+
+func GetWebReadTextToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "web_read_text",
+ DisplayName: "Read Web Page Text",
+ Description: "Get page text by CSS selector. Auto-refreshes. Returns clean text, no HTML.",
+ ShortDescription: "Read text from web widget",
+ ToolLogName: "web:readtext",
+ InputSchema: webSelectorSchema,
+ ToolCallDesc: webToolCallDesc("reading text"),
+ ToolTextCallback: func(input any) (string, error) {
+ parsed, err := parseWebSelectorInput(input)
+ if err != nil {
+ return "", err
+ }
+ return webReadContent(tabId, parsed, &wshrpc.WebSelectorOpts{InnerText: true, All: true, Highlight: true})
+ },
+ }
+}
+
+func GetWebReadHTMLToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "web_read_html",
+ DisplayName: "Read Web Page HTML",
+ Description: "Get innerHTML by CSS selector. Auto-refreshes. For inspecting page structure and attributes.",
+ ShortDescription: "Read HTML from web widget",
+ ToolLogName: "web:readhtml",
+ InputSchema: webSelectorSchema,
+ ToolCallDesc: webToolCallDesc("reading HTML"),
+ ToolTextCallback: func(input any) (string, error) {
+ parsed, err := parseWebSelectorInput(input)
+ if err != nil {
+ return "", err
+ }
+ return webReadContent(tabId, parsed, &wshrpc.WebSelectorOpts{Inner: true, All: true, Highlight: true})
+ },
+ }
+}
+
+const seoAuditJS = `
+const data = {};
+
+// Title
+data.title = document.title || '';
+
+// Meta tags
+const metas = {};
+document.querySelectorAll('meta[name], meta[property]').forEach(m => {
+ const key = m.getAttribute('name') || m.getAttribute('property');
+ if (key) metas[key] = m.getAttribute('content') || '';
+});
+data.meta = metas;
+
+// Canonical
+const canonical = document.querySelector('link[rel="canonical"]');
+data.canonical = canonical ? canonical.getAttribute('href') : null;
+
+// Hreflang
+const hreflangs = [];
+document.querySelectorAll('link[rel="alternate"][hreflang]').forEach(l => {
+ hreflangs.push({ lang: l.getAttribute('hreflang'), href: l.getAttribute('href') });
+});
+if (hreflangs.length) data.hreflang = hreflangs;
+
+// JSON-LD
+const jsonLd = [];
+document.querySelectorAll('script[type="application/ld+json"]').forEach(s => {
+ try { jsonLd.push(JSON.parse(s.textContent)); } catch(e) { jsonLd.push({ error: e.message, raw: s.textContent.slice(0, 500) }); }
+});
+if (jsonLd.length) data.jsonLd = jsonLd;
+
+// Open Graph
+const og = {};
+document.querySelectorAll('meta[property^="og:"]').forEach(m => {
+ og[m.getAttribute('property')] = m.getAttribute('content') || '';
+});
+if (Object.keys(og).length) data.openGraph = og;
+
+// Twitter Card
+const tw = {};
+document.querySelectorAll('meta[name^="twitter:"]').forEach(m => {
+ tw[m.getAttribute('name')] = m.getAttribute('content') || '';
+});
+if (Object.keys(tw).length) data.twitterCard = tw;
+
+// Headings structure
+const headings = {};
+['h1','h2','h3'].forEach(tag => {
+ const els = document.querySelectorAll(tag);
+ if (els.length) headings[tag] = Array.from(els).map(e => e.innerText.trim().slice(0, 100));
+});
+data.headings = headings;
+
+// Images without alt
+const imgsNoAlt = [];
+document.querySelectorAll('img:not([alt]), img[alt=""]').forEach(img => {
+ imgsNoAlt.push(img.src?.slice(0, 200) || img.getAttribute('data-src')?.slice(0, 200) || '[inline]');
+});
+if (imgsNoAlt.length) data.imagesWithoutAlt = imgsNoAlt;
+
+// Links count
+data.links = {
+ internal: document.querySelectorAll('a[href^="/"], a[href^="' + location.origin + '"]').length,
+ external: document.querySelectorAll('a[href^="http"]').length - document.querySelectorAll('a[href^="' + location.origin + '"]').length,
+ nofollow: document.querySelectorAll('a[rel*="nofollow"]').length,
+};
+
+// URL
+data.url = location.href;
+
+return JSON.stringify(data, null, 2);
+`
+
+func GetWebSEOAuditToolDefinition(tabId string) uctypes.ToolDefinition {
+ return uctypes.ToolDefinition{
+ Name: "web_seo_audit",
+ DisplayName: "SEO Audit",
+ Description: "Full SEO audit: title, meta, canonical, hreflang, JSON-LD, OG, Twitter Card, headings, alt text, links. Auto-refreshes.",
+ ShortDescription: "SEO audit of web page",
+ ToolLogName: "web:seoaudit",
+ InputSchema: map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "widget_id": map[string]any{
+ "type": "string",
+ "description": "8-character widget ID of the web browser widget",
+ },
+ },
+ "required": []string{"widget_id"},
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ inputMap, _ := input.(map[string]any)
+ widgetId, _ := inputMap["widget_id"].(string)
+ return fmt.Sprintf("running SEO audit on web widget %s", widgetId)
+ },
+ ToolTextCallback: func(input any) (string, error) {
+ parsed, err := parseWebSelectorInput(input)
+ if err != nil {
+ return "", err
+ }
+ return webReadContent(tabId, parsed, &wshrpc.WebSelectorOpts{ExecJs: seoAuditJS})
+ },
+ }
+}
diff --git a/pkg/aiusechat/tools_writefile.go b/pkg/aiusechat/tools_writefile.go
index d554cfab09..05c917a230 100644
--- a/pkg/aiusechat/tools_writefile.go
+++ b/pkg/aiusechat/tools_writefile.go
@@ -8,11 +8,11 @@ import (
"os"
"path/filepath"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/filebackup"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/filebackup"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const MaxEditFileSize = 100 * 1024 // 100KB
@@ -184,7 +184,7 @@ func GetWriteTextFileToolDefinition() uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "write_text_file",
DisplayName: "Write Text File",
- Description: "Write a text file to the filesystem. Will create or overwrite the file. Maximum file size: 100KB.",
+ Description: "Create or overwrite a text file. Max 100KB.",
ToolLogName: "gen:writefile",
Strict: true,
InputSchema: map[string]any{
@@ -349,10 +349,7 @@ func GetEditTextFileToolDefinition() uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "edit_text_file",
DisplayName: "Edit Text File",
- Description: "Edit a text file using precise search and replace. " +
- "Each old_str must appear EXACTLY ONCE in the file or the edit will fail. " +
- "All edits are applied atomically - if any single edit fails, the entire operation fails and no changes are made. " +
- "Maximum file size: 100KB.",
+ Description: "Edit file via search-and-replace. Each old_str must be unique. Atomic: all edits succeed or none apply. Max 100KB.",
ToolLogName: "gen:editfile",
Strict: true,
InputSchema: map[string]any{
@@ -496,7 +493,7 @@ func GetDeleteTextFileToolDefinition() uctypes.ToolDefinition {
return uctypes.ToolDefinition{
Name: "delete_text_file",
DisplayName: "Delete Text File",
- Description: "Delete a text file from the filesystem. A backup is created before deletion. Maximum file size: 100KB.",
+ Description: "Delete a text file. Backup created before deletion.",
ToolLogName: "gen:deletefile",
Strict: true,
InputSchema: map[string]any{
diff --git a/pkg/aiusechat/uctypes/uctypes.go b/pkg/aiusechat/uctypes/uctypes.go
index d2b25bbc1b..e716d27691 100644
--- a/pkg/aiusechat/uctypes/uctypes.go
+++ b/pkg/aiusechat/uctypes/uctypes.go
@@ -505,6 +505,7 @@ type WaveChatOpts struct {
TabStateGenerator func() (string, []ToolDefinition, string, error)
BuilderAppGenerator func() (string, string, string, error)
WidgetAccess bool
+ MCPAccess bool
AllowNativeWebSearch bool
BuilderId string
BuilderAppId string
@@ -516,6 +517,9 @@ type WaveChatOpts struct {
AppGoFile string
AppStaticFiles string
PlatformInfo string
+ MCPState string
+ MCPTools []ToolDefinition
+ MCPCwd string
}
func (opts *WaveChatOpts) GetToolDefinition(toolName string) *ToolDefinition {
@@ -529,6 +533,11 @@ func (opts *WaveChatOpts) GetToolDefinition(toolName string) *ToolDefinition {
return &tool
}
}
+ for _, tool := range opts.MCPTools {
+ if tool.Name == toolName {
+ return &tool
+ }
+ }
return nil
}
diff --git a/pkg/aiusechat/usechat-backend.go b/pkg/aiusechat/usechat-backend.go
index 37e2f432ec..8cd89b0e76 100644
--- a/pkg/aiusechat/usechat-backend.go
+++ b/pkg/aiusechat/usechat-backend.go
@@ -7,12 +7,12 @@ import (
"context"
"fmt"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/anthropic"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/gemini"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/openai"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/openaichat"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/aiusechat/anthropic"
+ "github.com/woveterm/wove/pkg/aiusechat/gemini"
+ "github.com/woveterm/wove/pkg/aiusechat/openai"
+ "github.com/woveterm/wove/pkg/aiusechat/openaichat"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/web/sse"
)
// UseChatBackend defines the interface for AI chat backend providers (OpenAI, Anthropic, etc.)
diff --git a/pkg/aiusechat/usechat-mode.go b/pkg/aiusechat/usechat-mode.go
index 1b1875202e..de3d0be7d9 100644
--- a/pkg/aiusechat/usechat-mode.go
+++ b/pkg/aiusechat/usechat-mode.go
@@ -9,10 +9,10 @@ import (
"os"
"regexp"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
)
var AzureResourceNameRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`)
diff --git a/pkg/aiusechat/usechat-prompts.go b/pkg/aiusechat/usechat-prompts.go
index 2d479c5246..75be10ea3e 100644
--- a/pkg/aiusechat/usechat-prompts.go
+++ b/pkg/aiusechat/usechat-prompts.go
@@ -6,78 +6,44 @@ package aiusechat
import "strings"
var SystemPromptText_OpenAI = strings.Join([]string{
- `You are Wave AI, an assistant embedded in Wave Terminal (a terminal with graphical widgets).`,
- `You appear as a pull-out panel on the left; widgets are on the right.`,
+ // Identity
+ `You are Wave AI, a senior software engineer embedded in Wave Terminal.`,
- // Capabilities & truthfulness
- `Tools define your only capabilities. If a capability is not provided by a tool, you cannot do it. Never fabricate data or pretend to call tools. If you lack data or access, say so directly and suggest the next best step.`,
- `Use read-only tools (capture_screenshot, read_text_file, read_dir, term_get_scrollback) automatically whenever they help answer the user's request. When a user clearly expresses intent to modify something (write/edit/delete files), call the corresponding tool directly.`,
+ // How to approach code tasks
+ `Before writing any code: 1) call wave_utils(action='project_instructions') to get section list, then call again with sections=[...all relevant sections...] to read FULL project rules, 2) read 2-3 existing sibling files to match their style exactly, 3) if MCP is available, query database schema for table relationships, 4) create a plan with wave_utils(action='plan_create').`,
- // Crisp behavior
- `Be concise and direct. Prefer determinism over speculation. If a brief clarifying question eliminates guesswork, ask it.`,
+ // Plan quality
+ `Plans must be detailed - act as a software architect. Embed specific rules from project_instructions into each step's details (e.g. "use Inertia props not axios", "add PHPDoc @return array{...}", "use Eloquent scopes not raw queries"). Each step must include: exact file path, reference file to copy pattern from, and acceptance criteria. Never create vague steps.`,
- // Attached text files
- `User-attached text files may appear inline as \ncontent\n .`,
- `User-attached directories use the tag JSON DirInfo .`,
- `If multiple attached files exist, treat each as a separate source file with its own file_name.`,
- `When the user refers to these files, use their inline content directly; do NOT call any read_text_file or file-access tools to re-read them unless asked.`,
+ // Code quality
+ `Match existing code style exactly - same naming conventions, same patterns, same structure. When you see the project uses static methods, use static methods. When it uses Eloquent scopes, use scopes. When components use Composition API, use Composition API. Read before you write. Comments in English only, only where logic is not self-evident.`,
- // Output & formatting
- `When presenting commands or any runnable multi-line code, always use fenced Markdown code blocks.`,
- `Use an appropriate language hint after the opening fence (e.g., "bash" for shell commands, "go" for Go, "json" for JSON).`,
- `For shell commands, do NOT prefix lines with "$" or shell prompts. Use placeholders in ALL_CAPS (e.g., PROJECT_ID) and explain them once after the block if needed.`,
- "Reserve inline code (single backticks) for short references like command names (`grep`, `less`), flags, env vars, file paths, or tiny snippets not meant to be executed.",
- `You may use Markdown (lists, tables, bold/italics) to improve readability.`,
- `Never comment on or justify your formatting choices; just follow these rules.`,
- `When generating code or command blocks, try to keep lines under ~100 characters wide where practical (soft wrap; do not break tokens mid-word). Favor indentation and short variable names to stay compact, but correctness always takes priority.`,
+ // Tool usage
+ `Use tools proactively: run CLI commands directly (not show them), grep/find to search code, read_text_file to check existing patterns. After writing files, run syntax checks and linters. Use MCP tools to verify data assumptions.`,
- // Safety & limits
- `If a request would execute dangerous or destructive actions, warn briefly and provide a safer alternative.`,
- `If output is very long, prefer a brief summary plus a copy-ready fenced block or offer a follow-up chunking strategy.`,
+ // Execution
+ `Execute plan one step at a time. After each step call wave_utils(action='plan_update') and immediately continue with the next step. NEVER stop to ask "should I continue?" or "do you want me to proceed?" - always continue until the plan is complete. If you see , continue the next pending step immediately. After writing code, re-read what you wrote and compare with the sibling file you used as reference - fix any inconsistencies before moving on.`,
- `You can write and edit local files on disk using available tools, but you cannot execute shell commands, run code in the terminal, or read/write remote files.`,
- `If the user asks you to execute commands or run code, or deal with remote files say that these features aren't available yet AND make sure to say that they are coming soon (stay tuned for updates).`,
- `Instead, show them exactly what command or code they could copy-paste to run manually.`,
+ // Attached files
+ `User-attached files appear as or tags. Use their content directly without re-reading.`,
- // Final reminder
- `You have NO API access to widgets or Wave unless provided via an explicit tool.`,
+ // Output
+ `Use fenced code blocks with language hints. Be concise in explanations but thorough in code.`,
}, " ")
var SystemPromptText_NoTools = strings.Join([]string{
- `You are Wave AI, an assistant embedded in Wave Terminal (a terminal with graphical widgets).`,
- `You appear as a pull-out panel on the left; widgets are on the right.`,
-
- // Capabilities & truthfulness
- `Be truthful about your capabilities. You can answer questions, explain concepts, provide code examples, and help with technical problems, but you cannot directly access files, execute commands, or interact with the terminal. If you lack specific data or access, say so directly and suggest what the user could do to provide it.`,
-
- // Crisp behavior
- `Be concise and direct. Prefer determinism over speculation. If a brief clarifying question eliminates guesswork, ask it.`,
-
- // Attached text files
- `User-attached text files may appear inline as \ncontent\n .`,
- `User-attached directories use the tag JSON DirInfo .`,
- `If multiple attached files exist, treat each as a separate source file with its own file_name.`,
- `When the user refers to these files, use their inline content directly for analysis and discussion.`,
-
- // Output & formatting
- `When presenting commands or any runnable multi-line code, always use fenced Markdown code blocks.`,
- `Use an appropriate language hint after the opening fence (e.g., "bash" for shell commands, "go" for Go, "json" for JSON).`,
- `For shell commands, do NOT prefix lines with "$" or shell prompts. Use placeholders in ALL_CAPS (e.g., PROJECT_ID) and explain them once after the block if needed.`,
- "Reserve inline code (single backticks) for short references like command names (`grep`, `less`), flags, env vars, file paths, or tiny snippets not meant to be executed.",
- `You may use Markdown (lists, tables, bold/italics) to improve readability.`,
- `Never comment on or justify your formatting choices; just follow these rules.`,
- `When generating code or command blocks, try to keep lines under ~100 characters wide where practical (soft wrap; do not break tokens mid-word). Favor indentation and short variable names to stay compact, but correctness always takes priority.`,
-
- // Safety & limits
- `If a request would execute dangerous or destructive actions, warn briefly and provide a safer alternative.`,
- `If output is very long, prefer a brief summary plus a copy-ready fenced block or offer a follow-up chunking strategy.`,
-
- `You cannot directly write files, execute shell commands, run code in the terminal, or access remote files.`,
- `When users ask for code or commands, provide ready-to-use examples they can copy and execute themselves.`,
- `If they need file modifications, show the exact changes they should make.`,
+ `You are Wave AI, a senior software engineer embedded in Wave Terminal.`,
+ `You cannot access files or run commands directly. Provide ready-to-use code that matches common project conventions. If you need more context, ask the user to share specific files.`,
+ `User-attached files appear as or tags. Use their content directly.`,
+ `Use fenced code blocks with language hints. Comments in English only, only where logic is not self-evident.`,
+}, " ")
- // Final reminder
- `You have NO API access to widgets or Wave Terminal internals.`,
+var SystemPromptText_MCPAddOn = strings.Join([]string{
+ `MCP tools (prefixed "mcp_") connect to the project's backend.`,
+ `Before writing database queries: call mcp_database-schema to check table structure and relationships.`,
+ `Before suggesting framework patterns: call mcp_search-docs for version-specific documentation.`,
+ `Before debugging: call mcp_last-error and mcp_read-log-entries to see actual errors.`,
+ `The block contains live project data. Cross-reference it with your code.`,
}, " ")
var SystemPromptText_StrictToolAddOn = `## Tool Call Rules (STRICT)
diff --git a/pkg/aiusechat/usechat-utils.go b/pkg/aiusechat/usechat-utils.go
index 72a5948d2b..aadd827e39 100644
--- a/pkg/aiusechat/usechat-utils.go
+++ b/pkg/aiusechat/usechat-utils.go
@@ -4,7 +4,7 @@
package aiusechat
import (
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
)
// CombineConsecutiveSameRoleMessages combines consecutive UIMessages with the same role
diff --git a/pkg/aiusechat/usechat.go b/pkg/aiusechat/usechat.go
index a55a10060a..f4c3397de6 100644
--- a/pkg/aiusechat/usechat.go
+++ b/pkg/aiusechat/usechat.go
@@ -18,27 +18,34 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/secretstore"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/util/ds"
- "github.com/wavetermdev/waveterm/pkg/util/logutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveappstore"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/aiusechat/aiutil"
+ "github.com/woveterm/wove/pkg/aiusechat/aiplan"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/projectctx"
+ "github.com/woveterm/wove/pkg/aiusechat/sessionhistory"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/secretstore"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/util/ds"
+ "github.com/woveterm/wove/pkg/util/logutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveappstore"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/web/sse"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
)
const DefaultAPI = uctypes.APIType_OpenAIResponses
-const DefaultMaxTokens = 4 * 1024
+const DefaultMaxTokens = 16 * 1024
const BuilderMaxTokens = 24 * 1024
+func init() {
+ sessionhistory.SaveAllCallback = saveAllSessionHistories
+}
+
var (
globalRateLimitInfo = &uctypes.RateLimitInfo{Unknown: true}
rateLimitLock sync.Mutex
@@ -46,7 +53,7 @@ var (
activeChats = ds.MakeSyncMap[bool]() // key is chatid
)
-func getSystemPrompt(apiType string, model string, isBuilder bool, hasToolsCapability bool, widgetAccess bool) []string {
+func getSystemPrompt(apiType string, model string, isBuilder bool, hasToolsCapability bool, widgetAccess bool, mcpAccess bool) []string {
if isBuilder {
return []string{}
}
@@ -55,12 +62,17 @@ func getSystemPrompt(apiType string, model string, isBuilder bool, hasToolsCapab
if useNoToolsPrompt {
basePrompt = SystemPromptText_NoTools
}
+ dateInfo := fmt.Sprintf("Current date: %s", time.Now().Format("2006-01-02 (Monday)"))
+ prompts := []string{basePrompt, dateInfo}
modelLower := strings.ToLower(model)
needsStrictToolAddOn, _ := regexp.MatchString(`(?i)\b(mistral|o?llama|qwen|mixtral|yi|phi|deepseek)\b`, modelLower)
if needsStrictToolAddOn && !useNoToolsPrompt {
- return []string{basePrompt, SystemPromptText_StrictToolAddOn}
+ prompts = append(prompts, SystemPromptText_StrictToolAddOn)
+ }
+ if mcpAccess {
+ prompts = append(prompts, SystemPromptText_MCPAddOn)
}
- return []string{basePrompt}
+ return prompts
}
func isLocalEndpoint(endpoint string) bool {
@@ -178,12 +190,23 @@ func GetGlobalRateLimit() *uctypes.RateLimitInfo {
return globalRateLimitInfo
}
+const DefaultAITimeoutMs = 90000 // 90 seconds default timeout for AI requests
+
func runAIChatStep(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseChatBackend, chatOpts uctypes.WaveChatOpts, cont *uctypes.WaveContinueResponse) (*uctypes.WaveStopReason, []uctypes.GenAIMessage, error) {
if chatOpts.Config.APIType == uctypes.APIType_OpenAIResponses && shouldUseChatCompletionsAPI(chatOpts.Config.Model) {
return nil, nil, fmt.Errorf("Chat completions API not available (must use newer OpenAI models)")
}
+ // Apply default timeout if none is configured
+ if chatOpts.Config.TimeoutMs <= 0 {
+ chatOpts.Config.TimeoutMs = DefaultAITimeoutMs
+ }
stopReason, messages, rateLimitInfo, err := backend.RunChatStep(ctx, sseHandler, chatOpts, cont)
updateRateLimit(rateLimitInfo)
+ if err != nil {
+ if ctx.Err() != nil {
+ return nil, nil, fmt.Errorf("request timed out - check your internet connection and try again")
+ }
+ }
return stopReason, messages, err
}
@@ -431,6 +454,15 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha
chatOpts.PlatformInfo = platformInfo
}
}
+ if chatOpts.MCPAccess && firstStep {
+ mcpState, mcpTools, mcpErr := generateMCPStateAndTools(chatOpts)
+ if mcpErr == nil {
+ chatOpts.MCPState = mcpState
+ chatOpts.MCPTools = mcpTools
+ } else {
+ log.Printf("[mcp] warning: failed to generate MCP context: %v\n", mcpErr)
+ }
+ }
stopReason, rtnMessages, err := runAIChatStep(ctx, sseHandler, backend, chatOpts, cont)
metrics.RequestCount++
if chatOpts.Config.IsWaveProxy() {
@@ -629,6 +661,8 @@ type PostMessageRequest struct {
ChatID string `json:"chatid"`
Msg uctypes.AIMessage `json:"msg"`
WidgetAccess bool `json:"widgetaccess,omitempty"`
+ MCPAccess bool `json:"mcpaccess,omitempty"`
+ MCPCwd string `json:"mcpcwd,omitempty"`
AIMode string `json:"aimode"`
}
@@ -688,17 +722,71 @@ func WaveAIPostMessageHandler(w http.ResponseWriter, r *http.Request) {
ClientId: wstore.GetClientId(),
Config: *aiOpts,
WidgetAccess: req.WidgetAccess,
+ MCPAccess: req.MCPAccess,
AllowNativeWebSearch: true,
BuilderId: req.BuilderId,
BuilderAppId: req.BuilderAppId,
}
- chatOpts.SystemPrompt = getSystemPrompt(chatOpts.Config.APIType, chatOpts.Config.Model, chatOpts.BuilderId != "", chatOpts.Config.HasCapability(uctypes.AICapabilityTools), chatOpts.WidgetAccess)
+ chatOpts.SystemPrompt = getSystemPrompt(chatOpts.Config.APIType, chatOpts.Config.Model, chatOpts.BuilderId != "", chatOpts.Config.HasCapability(uctypes.AICapabilityTools), chatOpts.WidgetAccess, chatOpts.MCPAccess)
+
+ // Register chat-tab mapping for session history persistence
+ if req.TabId != "" {
+ sessionhistory.RegisterChatTab(req.ChatID, req.TabId)
+ }
+
+ // Inject project context
+ if req.TabId != "" {
+ cwd := getTerminalCwd(r.Context(), req.TabId)
+ if cwd != "" {
+ // Project stack info (name, tech stack, architecture) - always injected (~50 tokens)
+ if stack := projectctx.ExtractProjectStack(cwd); stack != "" {
+ chatOpts.SystemPrompt = append(chatOpts.SystemPrompt, stack)
+ }
+ // Critical rules from CLAUDE.md/WAVE.md - always injected (~100 tokens)
+ if rules := projectctx.ExtractCriticalRules(cwd); rules != "" {
+ chatOpts.SystemPrompt = append(chatOpts.SystemPrompt, rules)
+ }
+ // First message only: project structure + hints
+ if chatstore.DefaultChatStore.CountUserMessages(req.ChatID) == 0 {
+ if tree := projectctx.GetProjectTree(cwd, 2); tree != "" {
+ chatOpts.SystemPrompt = append(chatOpts.SystemPrompt, tree)
+ }
+ // Hints about available context (compact)
+ var hints []string
+ if projectctx.FindInstructionsFile(cwd) != "" {
+ hints = append(hints, "project_instructions available via wave_utils")
+ }
+ if sessionhistory.LoadSessionHistory(req.TabId) != "" {
+ hints = append(hints, "previous session history available via wave_utils(action='session_history')")
+ }
+ if len(hints) > 0 {
+ chatOpts.SystemPrompt = append(chatOpts.SystemPrompt, "Context: "+strings.Join(hints, "; ")+".")
+ }
+ }
+ }
+ }
+
+ // Inject active plan status if one exists for this tab
+ if req.TabId != "" {
+ activePlan := aiplan.GetPlan(req.TabId)
+ if activePlan != nil && !aiplan.IsComplete(activePlan) {
+ chatOpts.SystemPrompt = append(chatOpts.SystemPrompt, aiplan.FormatPlanStatus(activePlan))
+ }
+ }
if req.TabId != "" {
chatOpts.TabStateGenerator = func() (string, []uctypes.ToolDefinition, string, error) {
tabState, tabTools, err := GenerateTabStateAndTools(r.Context(), req.TabId, req.WidgetAccess, &chatOpts)
return tabState, tabTools, req.TabId, err
}
+ // Extract CWD for MCP context: prefer explicit mcpcwd, fallback to terminal CWD
+ if req.MCPAccess {
+ if req.MCPCwd != "" {
+ chatOpts.MCPCwd = req.MCPCwd
+ } else {
+ chatOpts.MCPCwd = getTerminalCwd(r.Context(), req.TabId)
+ }
+ }
}
if req.BuilderAppId != "" {
@@ -882,3 +970,26 @@ func generateBuilderAppData(appId string) (string, string, string, error) {
return appGoFile, staticFilesJSON, platformInfo, nil
}
+
+// saveAllSessionHistories converts all in-memory chats to UIChat and saves as session history.
+func saveAllSessionHistories() {
+ allChats := chatstore.DefaultChatStore.GetAll()
+ mappings := sessionhistory.GetAllMappings()
+ saved := 0
+ for chatId, aiChat := range allChats {
+ tabId, ok := mappings[chatId]
+ if !ok || tabId == "" {
+ continue
+ }
+ uiChat, err := ConvertAIChatToUIChat(aiChat)
+ if err != nil {
+ log.Printf("[sessionhistory] error converting chat %s: %v\n", chatId[:8], err)
+ continue
+ }
+ sessionhistory.SaveChatAsHistory(tabId, uiChat)
+ saved++
+ }
+ if saved > 0 {
+ log.Printf("[sessionhistory] saved %d session histories at shutdown\n", saved)
+ }
+}
diff --git a/pkg/aiusechat/usechat_mode_test.go b/pkg/aiusechat/usechat_mode_test.go
index 73959f9585..1eb9c8fa53 100644
--- a/pkg/aiusechat/usechat_mode_test.go
+++ b/pkg/aiusechat/usechat_mode_test.go
@@ -6,8 +6,8 @@ package aiusechat
import (
"testing"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/wconfig"
)
func TestApplyProviderDefaultsGroq(t *testing.T) {
diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go
index 75f1938e12..e12d9f2274 100644
--- a/pkg/blockcontroller/blockcontroller.go
+++ b/pkg/blockcontroller/blockcontroller.go
@@ -14,19 +14,19 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/jobcontroller"
- "github.com/wavetermdev/waveterm/pkg/remote"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/util/ds"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wslconn"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/jobcontroller"
+ "github.com/woveterm/wove/pkg/remote"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/util/ds"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wslconn"
+ "github.com/woveterm/wove/pkg/wstore"
)
const (
diff --git a/pkg/blockcontroller/durableshellcontroller.go b/pkg/blockcontroller/durableshellcontroller.go
index d3481b172d..87fe716b01 100644
--- a/pkg/blockcontroller/durableshellcontroller.go
+++ b/pkg/blockcontroller/durableshellcontroller.go
@@ -12,19 +12,19 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/jobcontroller"
- "github.com/wavetermdev/waveterm/pkg/remote"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/shellexec"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "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"
+ "github.com/woveterm/wove/pkg/jobcontroller"
+ "github.com/woveterm/wove/pkg/remote"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/shellexec"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wstore"
)
type DurableShellController struct {
diff --git a/pkg/blockcontroller/shellcontroller.go b/pkg/blockcontroller/shellcontroller.go
index a410225394..fd886bddd1 100644
--- a/pkg/blockcontroller/shellcontroller.go
+++ b/pkg/blockcontroller/shellcontroller.go
@@ -16,26 +16,26 @@ import (
"sync/atomic"
"time"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/shellexec"
- "github.com/wavetermdev/waveterm/pkg/util/envutil"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
- "github.com/wavetermdev/waveterm/pkg/wslconn"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/shellexec"
+ "github.com/woveterm/wove/pkg/util/envutil"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wslconn"
+ "github.com/woveterm/wove/pkg/wstore"
)
const (
diff --git a/pkg/blockcontroller/tsunamicontroller.go b/pkg/blockcontroller/tsunamicontroller.go
index d064d87998..50d81341d3 100644
--- a/pkg/blockcontroller/tsunamicontroller.go
+++ b/pkg/blockcontroller/tsunamicontroller.go
@@ -15,16 +15,16 @@ import (
"sync"
"syscall"
- "github.com/wavetermdev/waveterm/pkg/tsunamiutil"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/waveappstore"
- "github.com/wavetermdev/waveterm/pkg/waveapputil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
- "github.com/wavetermdev/waveterm/tsunami/build"
+ "github.com/woveterm/wove/pkg/tsunamiutil"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/waveappstore"
+ "github.com/woveterm/wove/pkg/waveapputil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
+ "github.com/woveterm/wove/tsunami/build"
)
type TsunamiAppProc struct {
diff --git a/pkg/blocklogger/blocklogger.go b/pkg/blocklogger/blocklogger.go
index c7d6f79af0..a9beb0b191 100644
--- a/pkg/blocklogger/blocklogger.go
+++ b/pkg/blocklogger/blocklogger.go
@@ -10,8 +10,8 @@ import (
"log"
"strings"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
)
// Buffer size for the output channel
diff --git a/pkg/buildercontroller/buildercontroller.go b/pkg/buildercontroller/buildercontroller.go
index fd12898e82..b064b90e45 100644
--- a/pkg/buildercontroller/buildercontroller.go
+++ b/pkg/buildercontroller/buildercontroller.go
@@ -16,17 +16,17 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/tsunamiutil"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/waveappstore"
- "github.com/wavetermdev/waveterm/pkg/waveapputil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/tsunami/build"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/tsunamiutil"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/waveappstore"
+ "github.com/woveterm/wove/pkg/waveapputil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/tsunami/build"
)
const (
diff --git a/pkg/faviconcache/faviconcache.go b/pkg/faviconcache/faviconcache.go
index 3fa12dbee8..c15cd1e145 100644
--- a/pkg/faviconcache/faviconcache.go
+++ b/pkg/faviconcache/faviconcache.go
@@ -15,7 +15,7 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/panichandler"
)
// --- Constants and Types ---
diff --git a/pkg/filebackup/filebackup.go b/pkg/filebackup/filebackup.go
index eea4905dcc..b4be26fa91 100644
--- a/pkg/filebackup/filebackup.go
+++ b/pkg/filebackup/filebackup.go
@@ -14,7 +14,7 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const BackupRetentionPeriod = 5 * 24 * time.Hour
diff --git a/pkg/filestore/blockstore.go b/pkg/filestore/blockstore.go
index 55ce70183a..bbc38b617c 100644
--- a/pkg/filestore/blockstore.go
+++ b/pkg/filestore/blockstore.go
@@ -17,9 +17,9 @@ import (
"sync/atomic"
"time"
- "github.com/wavetermdev/waveterm/pkg/ijson"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/ijson"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const (
diff --git a/pkg/filestore/blockstore_dbops.go b/pkg/filestore/blockstore_dbops.go
index 5b7006da09..fe92fc1ae8 100644
--- a/pkg/filestore/blockstore_dbops.go
+++ b/pkg/filestore/blockstore_dbops.go
@@ -9,7 +9,7 @@ import (
"io/fs"
"os"
- "github.com/wavetermdev/waveterm/pkg/util/dbutil"
+ "github.com/woveterm/wove/pkg/util/dbutil"
)
// can return fs.ErrExist
diff --git a/pkg/filestore/blockstore_dbsetup.go b/pkg/filestore/blockstore_dbsetup.go
index f7afde3149..5d2402ef86 100644
--- a/pkg/filestore/blockstore_dbsetup.go
+++ b/pkg/filestore/blockstore_dbsetup.go
@@ -13,14 +13,14 @@ import (
"path/filepath"
"time"
- "github.com/wavetermdev/waveterm/pkg/util/migrateutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/util/migrateutil"
+ "github.com/woveterm/wove/pkg/wavebase"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/sawka/txwrap"
- dbfs "github.com/wavetermdev/waveterm/db"
+ dbfs "github.com/woveterm/wove/db"
)
const FilestoreDBName = "filestore.db"
diff --git a/pkg/filestore/blockstore_test.go b/pkg/filestore/blockstore_test.go
index 8d1b3b04d4..046a7ffc92 100644
--- a/pkg/filestore/blockstore_test.go
+++ b/pkg/filestore/blockstore_test.go
@@ -17,8 +17,8 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/ijson"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/ijson"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
func initDb(t *testing.T) {
diff --git a/pkg/genconn/genconn.go b/pkg/genconn/genconn.go
index 302600cd85..de17a8f8dc 100644
--- a/pkg/genconn/genconn.go
+++ b/pkg/genconn/genconn.go
@@ -12,8 +12,8 @@ import (
"strings"
"sync"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/syncbuf"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/syncbuf"
)
type connContextKeyType struct{}
diff --git a/pkg/genconn/wsl-impl.go b/pkg/genconn/wsl-impl.go
index cc5dabfbd6..de5e1acb5d 100644
--- a/pkg/genconn/wsl-impl.go
+++ b/pkg/genconn/wsl-impl.go
@@ -9,7 +9,7 @@ import (
"io"
"sync"
- "github.com/wavetermdev/waveterm/pkg/wsl"
+ "github.com/woveterm/wove/pkg/wsl"
)
var _ ShellClient = (*WSLShellClient)(nil)
diff --git a/pkg/gogen/gogen.go b/pkg/gogen/gogen.go
index bc3662a678..1946debfb3 100644
--- a/pkg/gogen/gogen.go
+++ b/pkg/gogen/gogen.go
@@ -8,8 +8,8 @@ import (
"reflect"
"strings"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
func GenerateBoilerplate(buf *strings.Builder, pkgName string, imports []string) {
diff --git a/pkg/gogen/gogen_test.go b/pkg/gogen/gogen_test.go
index 2965d26f1f..bd854323d1 100644
--- a/pkg/gogen/gogen_test.go
+++ b/pkg/gogen/gogen_test.go
@@ -8,7 +8,7 @@ import (
"strings"
"testing"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
func TestGetWshMethodDataParamsAndExpr_MultiArg(t *testing.T) {
diff --git a/pkg/jobcontroller/jobcontroller.go b/pkg/jobcontroller/jobcontroller.go
index e41d77585c..9cba933623 100644
--- a/pkg/jobcontroller/jobcontroller.go
+++ b/pkg/jobcontroller/jobcontroller.go
@@ -15,28 +15,28 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/streamclient"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/util/ds"
- "github.com/wavetermdev/waveterm/pkg/util/envutil"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wavejwt"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "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"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/streamclient"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/util/ds"
+ "github.com/woveterm/wove/pkg/util/envutil"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavejwt"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wstore"
"golang.org/x/sync/singleflight"
)
diff --git a/pkg/jobmanager/jobcmd.go b/pkg/jobmanager/jobcmd.go
index 8adfabefc4..2523b15173 100644
--- a/pkg/jobmanager/jobcmd.go
+++ b/pkg/jobmanager/jobcmd.go
@@ -13,9 +13,9 @@ import (
"time"
"github.com/creack/pty"
- "github.com/wavetermdev/waveterm/pkg/util/unixutil"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/util/unixutil"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type CmdDef struct {
diff --git a/pkg/jobmanager/jobmanager.go b/pkg/jobmanager/jobmanager.go
index dd58bccc52..3bf76610d1 100644
--- a/pkg/jobmanager/jobmanager.go
+++ b/pkg/jobmanager/jobmanager.go
@@ -14,14 +14,14 @@ import (
"time"
"github.com/shirou/gopsutil/v4/process"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wavejwt"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavejwt"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const JobAccessTokenLabel = "Wave-JobAccessToken"
diff --git a/pkg/jobmanager/jobmanager_unix.go b/pkg/jobmanager/jobmanager_unix.go
index c9c07e1843..9fdfc8db34 100644
--- a/pkg/jobmanager/jobmanager_unix.go
+++ b/pkg/jobmanager/jobmanager_unix.go
@@ -13,7 +13,7 @@ import (
"path/filepath"
"syscall"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavebase"
"golang.org/x/sys/unix"
)
diff --git a/pkg/jobmanager/mainserverconn.go b/pkg/jobmanager/mainserverconn.go
index 33bb10cdfb..7bb8739ac1 100644
--- a/pkg/jobmanager/mainserverconn.go
+++ b/pkg/jobmanager/mainserverconn.go
@@ -11,11 +11,11 @@ import (
"sync"
"sync/atomic"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/wavejwt"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/wavejwt"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
type MainServerConn struct {
diff --git a/pkg/jobmanager/streammanager.go b/pkg/jobmanager/streammanager.go
index 4d77ed5acc..c4a324221b 100644
--- a/pkg/jobmanager/streammanager.go
+++ b/pkg/jobmanager/streammanager.go
@@ -10,7 +10,7 @@ import (
"log"
"sync"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const (
diff --git a/pkg/jobmanager/streammanager_test.go b/pkg/jobmanager/streammanager_test.go
index 9a0e3c895e..798f90f330 100644
--- a/pkg/jobmanager/streammanager_test.go
+++ b/pkg/jobmanager/streammanager_test.go
@@ -11,7 +11,7 @@ import (
"testing"
"time"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type testWriter struct {
diff --git a/pkg/mcpclient/config.go b/pkg/mcpclient/config.go
new file mode 100644
index 0000000000..7021a4f923
--- /dev/null
+++ b/pkg/mcpclient/config.go
@@ -0,0 +1,51 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package mcpclient
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+const MCPConfigFileName = ".mcp.json"
+
+// NormalizeMCPDir ensures the path is a directory, not a file path.
+// If a file path is given (e.g. /path/to/.mcp.json), it returns the parent directory.
+func NormalizeMCPDir(dir string) string {
+ info, err := os.Stat(dir)
+ if err == nil && !info.IsDir() {
+ return filepath.Dir(dir)
+ }
+ return dir
+}
+
+// LoadMCPConfig searches for .mcp.json in the given directory and returns parsed server configs.
+func LoadMCPConfig(dir string) (map[string]MCPServerConfig, error) {
+ dir = NormalizeMCPDir(dir)
+ configPath := filepath.Join(dir, MCPConfigFileName)
+ data, err := os.ReadFile(configPath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("reading %s: %w", configPath, err)
+ }
+ var config MCPConfigFile
+ if err := json.Unmarshal(data, &config); err != nil {
+ return nil, fmt.Errorf("parsing %s: %w", configPath, err)
+ }
+ // Set default CWD to the directory containing the config file
+ for name, sc := range config.McpServers {
+ if sc.Cwd == "" {
+ sc.Cwd = dir
+ }
+ if sc.Type == "" {
+ sc.Type = "stdio"
+ }
+ config.McpServers[name] = sc
+ }
+ return config.McpServers, nil
+}
diff --git a/pkg/mcpclient/handler.go b/pkg/mcpclient/handler.go
new file mode 100644
index 0000000000..6c212c2ca3
--- /dev/null
+++ b/pkg/mcpclient/handler.go
@@ -0,0 +1,211 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package mcpclient
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "sync"
+ "time"
+)
+
+// MCPStatusResponse is returned by the /api/mcp/status endpoint.
+type MCPStatusResponse struct {
+ Connected bool `json:"connected"`
+ ServerName string `json:"serverName,omitempty"`
+ ServerInfo MCPServerInfo `json:"serverInfo,omitempty"`
+ Tools []MCPTool `json:"tools,omitempty"`
+ Resources []MCPResource `json:"resources,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+// MCPCallRequest is the request body for /api/mcp/call.
+type MCPCallRequest struct {
+ Cwd string `json:"cwd"`
+ Server string `json:"server,omitempty"` // optional, defaults to first server
+ ToolName string `json:"toolName"`
+ Arguments map[string]any `json:"arguments,omitempty"`
+}
+
+// MCPCallResponse is the response from /api/mcp/call.
+type MCPCallResponse struct {
+ Result string `json:"result,omitempty"`
+ Error string `json:"error,omitempty"`
+ Duration float64 `json:"duration,omitempty"` // seconds
+}
+
+// MCPCallLogEntry represents a single tool call in the call log.
+type MCPCallLogEntry struct {
+ Timestamp time.Time `json:"timestamp"`
+ ToolName string `json:"toolName"`
+ Duration float64 `json:"duration"` // seconds
+ Error string `json:"error,omitempty"`
+ ResultLen int `json:"resultLen"`
+ Arguments map[string]any `json:"arguments,omitempty"`
+ Result string `json:"result,omitempty"`
+}
+
+// callLog stores recent MCP calls for the widget
+var (
+ callLog []MCPCallLogEntry
+ callLogMu sync.Mutex
+)
+
+const maxCallLogSize = 50
+
+func AddCallLog(entry MCPCallLogEntry) {
+ callLogMu.Lock()
+ defer callLogMu.Unlock()
+ callLog = append(callLog, entry)
+ if len(callLog) > maxCallLogSize {
+ callLog = callLog[len(callLog)-maxCallLogSize:]
+ }
+}
+
+func getCallLog() []MCPCallLogEntry {
+ callLogMu.Lock()
+ defer callLogMu.Unlock()
+ result := make([]MCPCallLogEntry, len(callLog))
+ copy(result, callLog)
+ return result
+}
+
+// HandleMCPStatus handles GET /api/mcp/status?cwd=...
+func HandleMCPStatus(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ cwd := r.URL.Query().Get("cwd")
+ if cwd == "" {
+ writeJSON(w, MCPStatusResponse{Connected: false, Error: "cwd parameter required"})
+ return
+ }
+
+ configs, err := LoadMCPConfig(cwd)
+ if err != nil || configs == nil {
+ writeJSON(w, MCPStatusResponse{Connected: false, Error: "no .mcp.json found"})
+ return
+ }
+
+ manager := GetManager()
+
+ // Try to connect to first available server
+ var resp MCPStatusResponse
+ for name := range configs {
+ client, err := manager.GetClient(cwd, name)
+ if err != nil {
+ resp = MCPStatusResponse{Connected: false, Error: fmt.Sprintf("failed to connect to %s: %v", name, err)}
+ continue
+ }
+ resp = MCPStatusResponse{
+ Connected: client.IsAlive(),
+ ServerName: name,
+ ServerInfo: client.ServerInfo(),
+ Tools: client.ListTools(),
+ Resources: client.ListResources(),
+ }
+ break
+ }
+
+ writeJSON(w, resp)
+}
+
+// HandleMCPCall handles POST /api/mcp/call
+func HandleMCPCall(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ writeJSON(w, MCPCallResponse{Error: "failed to read request body"})
+ return
+ }
+ defer r.Body.Close()
+
+ var req MCPCallRequest
+ if err := json.Unmarshal(body, &req); err != nil {
+ writeJSON(w, MCPCallResponse{Error: fmt.Sprintf("invalid request: %v", err)})
+ return
+ }
+
+ if req.Cwd == "" || req.ToolName == "" {
+ writeJSON(w, MCPCallResponse{Error: "cwd and toolName are required"})
+ return
+ }
+
+ manager := GetManager()
+
+ // Find the right server
+ serverName := req.Server
+ if serverName == "" {
+ configs, err := LoadMCPConfig(req.Cwd)
+ if err != nil || configs == nil {
+ writeJSON(w, MCPCallResponse{Error: "no .mcp.json found"})
+ return
+ }
+ for name := range configs {
+ serverName = name
+ break
+ }
+ }
+
+ client, err := manager.GetClient(req.Cwd, serverName)
+ if err != nil {
+ writeJSON(w, MCPCallResponse{Error: fmt.Sprintf("failed to connect: %v", err)})
+ return
+ }
+
+ start := time.Now()
+ result, err := client.CallTool(req.ToolName, req.Arguments)
+ duration := time.Since(start).Seconds()
+
+ logEntry := MCPCallLogEntry{
+ Timestamp: time.Now(),
+ ToolName: req.ToolName,
+ Duration: duration,
+ ResultLen: len(result),
+ Arguments: req.Arguments,
+ Result: truncateResult(result),
+ }
+
+ if err != nil {
+ logEntry.Error = err.Error()
+ logEntry.Result = ""
+ AddCallLog(logEntry)
+ writeJSON(w, MCPCallResponse{Error: err.Error(), Duration: duration})
+ return
+ }
+
+ AddCallLog(logEntry)
+ writeJSON(w, MCPCallResponse{Result: result, Duration: duration})
+}
+
+// HandleMCPCallLog handles GET /api/mcp/calllog
+func HandleMCPCallLog(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+ writeJSON(w, getCallLog())
+}
+
+const maxResultLogSize = 10000
+
+func truncateResult(s string) string {
+ if len(s) > maxResultLogSize {
+ return s[:maxResultLogSize] + "\n... [truncated]"
+ }
+ return s
+}
+
+func writeJSON(w http.ResponseWriter, data any) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(data)
+}
diff --git a/pkg/mcpclient/manager.go b/pkg/mcpclient/manager.go
new file mode 100644
index 0000000000..965ef5d204
--- /dev/null
+++ b/pkg/mcpclient/manager.go
@@ -0,0 +1,240 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package mcpclient
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+)
+
+var (
+ globalManager *MCPManager
+ globalManagerOnce sync.Once
+)
+
+// MCPManager manages MCP client instances per working directory.
+type MCPManager struct {
+ mu sync.Mutex
+ clients map[string]*MCPClient // keyed by "cwd:serverName"
+}
+
+// GetManager returns the singleton MCPManager.
+func GetManager() *MCPManager {
+ globalManagerOnce.Do(func() {
+ globalManager = &MCPManager{
+ clients: make(map[string]*MCPClient),
+ }
+ })
+ return globalManager
+}
+
+// GetClient returns an existing MCP client for the given CWD and server name,
+// or creates a new one by reading .mcp.json from that directory.
+func (m *MCPManager) GetClient(cwd string, serverName string) (*MCPClient, error) {
+ cwd = NormalizeMCPDir(cwd)
+ key := cwd + ":" + serverName
+
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ // Return existing alive client
+ if client, ok := m.clients[key]; ok && client.IsAlive() {
+ return client, nil
+ }
+
+ // Load config and create new client while holding the lock
+ // to prevent duplicate client creation from concurrent calls
+ configs, err := LoadMCPConfig(cwd)
+ if err != nil {
+ return nil, fmt.Errorf("loading MCP config from %s: %w", cwd, err)
+ }
+ if configs == nil {
+ return nil, fmt.Errorf("no .mcp.json found in %s", cwd)
+ }
+
+ config, ok := configs[serverName]
+ if !ok {
+ return nil, fmt.Errorf("MCP server %q not found in %s/.mcp.json", serverName, cwd)
+ }
+
+ client, err := NewMCPClient(serverName, config)
+ if err != nil {
+ return nil, err
+ }
+
+ m.clients[key] = client
+ return client, nil
+}
+
+// GetAllClients returns MCP clients for all servers defined in .mcp.json at the given CWD.
+func (m *MCPManager) GetAllClients(cwd string) ([]*MCPClient, error) {
+ configs, err := LoadMCPConfig(cwd)
+ if err != nil {
+ return nil, fmt.Errorf("loading MCP config from %s: %w", cwd, err)
+ }
+ if configs == nil {
+ return nil, nil
+ }
+
+ var clients []*MCPClient
+ for name := range configs {
+ client, err := m.GetClient(cwd, name)
+ if err != nil {
+ log.Printf("[mcpclient] warning: failed to connect to MCP server %q: %v\n", name, err)
+ continue
+ }
+ clients = append(clients, client)
+ }
+ return clients, nil
+}
+
+// GetAutoContext connects to all MCP servers in the given CWD and fetches
+// auto-context data (database-schema summary, application-info).
+// Returns formatted XML context string for injection into AI prompts.
+func (m *MCPManager) GetAutoContext(cwd string) (string, error) {
+ clients, err := m.GetAllClients(cwd)
+ if err != nil {
+ return "", err
+ }
+ if len(clients) == 0 {
+ return "", nil
+ }
+
+ var sb strings.Builder
+ sb.WriteString("\n")
+
+ for _, client := range clients {
+ sb.WriteString(fmt.Sprintf("\n", client.serverInfo.Name, client.serverInfo.Version))
+
+ // Try to get application-info (if tool exists)
+ if hasToolNamed(client.tools, "application-info") {
+ result, err := client.CallTool("application-info", map[string]any{})
+ if err == nil && result != "" {
+ sb.WriteString("\n")
+ sb.WriteString(result)
+ sb.WriteString("\n \n")
+ }
+ }
+
+ // Try to get database-schema summary (if tool exists)
+ if hasToolNamed(client.tools, "database-schema") {
+ result, err := client.CallTool("database-schema", map[string]any{"summary": true})
+ if err == nil && result != "" {
+ sb.WriteString("\n")
+ sb.WriteString(result)
+ sb.WriteString("\n \n")
+ }
+ }
+
+ sb.WriteString(" \n")
+ }
+
+ sb.WriteString(" ")
+ return sb.String(), nil
+}
+
+// GetToolDefinitions returns Wave ToolDefinitions for all MCP tools across all servers in the given CWD.
+// Each MCP tool is wrapped with a callback that calls the MCP server.
+func (m *MCPManager) GetToolDefinitions(cwd string) ([]uctypes.ToolDefinition, error) {
+ clients, err := m.GetAllClients(cwd)
+ if err != nil {
+ return nil, err
+ }
+
+ var tools []uctypes.ToolDefinition
+ for _, client := range clients {
+ for _, mcpTool := range client.tools {
+ tool := convertMCPToolToDefinition(client, mcpTool)
+ tools = append(tools, tool)
+ }
+ }
+ return tools, nil
+}
+
+// convertMCPToolToDefinition wraps an MCP tool as a Wave ToolDefinition.
+func convertMCPToolToDefinition(client *MCPClient, mcpTool MCPTool) uctypes.ToolDefinition {
+ // Prefix tool name with "mcp_" to avoid collisions with built-in tools
+ toolName := "mcp_" + mcpTool.Name
+
+ // Capture client and tool name for the callback closure
+ capturedClient := client
+ capturedToolName := mcpTool.Name
+
+ return uctypes.ToolDefinition{
+ Name: toolName,
+ DisplayName: fmt.Sprintf("MCP: %s", mcpTool.Name),
+ Description: mcpTool.Description,
+ ShortDescription: fmt.Sprintf("MCP tool from %s", client.serverName),
+ ToolLogName: fmt.Sprintf("mcp:%s:%s", client.serverName, mcpTool.Name),
+ InputSchema: mcpTool.InputSchema,
+ ToolTextCallback: func(input any) (string, error) {
+ // Convert input to map[string]any for MCP call
+ args, ok := input.(map[string]any)
+ if !ok && input != nil {
+ // Try JSON round-trip for struct inputs
+ data, err := json.Marshal(input)
+ if err != nil {
+ return "", fmt.Errorf("marshaling tool input: %w", err)
+ }
+ args = make(map[string]any)
+ if err := json.Unmarshal(data, &args); err != nil {
+ return "", fmt.Errorf("converting tool input: %w", err)
+ }
+ }
+ start := time.Now()
+ result, err := capturedClient.CallTool(capturedToolName, args)
+ duration := time.Since(start).Seconds()
+ logEntry := MCPCallLogEntry{
+ Timestamp: time.Now(),
+ ToolName: capturedToolName,
+ Duration: duration,
+ ResultLen: len(result),
+ Arguments: args,
+ Result: truncateResult(result),
+ }
+ if err != nil {
+ logEntry.Error = err.Error()
+ logEntry.Result = ""
+ }
+ AddCallLog(logEntry)
+ return result, err
+ },
+ ToolCallDesc: func(input any, output any, _ *uctypes.UIMessageDataToolUse) string {
+ return fmt.Sprintf("Called MCP tool: %s", capturedToolName)
+ },
+ }
+}
+
+// HasMCPConfig checks if a .mcp.json file exists in the given directory.
+func HasMCPConfig(cwd string) bool {
+ configs, err := LoadMCPConfig(cwd)
+ return err == nil && configs != nil && len(configs) > 0
+}
+
+// Shutdown closes all active MCP clients.
+func (m *MCPManager) Shutdown() {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ for key, client := range m.clients {
+ client.Close()
+ delete(m.clients, key)
+ }
+ log.Println("[mcpclient] all MCP clients shut down")
+}
+
+func hasToolNamed(tools []MCPTool, name string) bool {
+ for _, t := range tools {
+ if t.Name == name {
+ return true
+ }
+ }
+ return false
+}
diff --git a/pkg/mcpclient/mcpclient.go b/pkg/mcpclient/mcpclient.go
new file mode 100644
index 0000000000..7dcd19856e
--- /dev/null
+++ b/pkg/mcpclient/mcpclient.go
@@ -0,0 +1,313 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package mcpclient
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "os/exec"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+const (
+ MCPProtocolVersion = "2024-11-05"
+ MCPClientName = "waveterm"
+ MCPClientVersion = "0.1.0"
+ ReadTimeout = 30 * time.Second
+ ShutdownTimeout = 5 * time.Second
+)
+
+type MCPClient struct {
+ serverName string
+ cmd *exec.Cmd
+ stdin io.WriteCloser
+ stdout *bufio.Reader
+ mu sync.Mutex
+ nextId atomic.Int64
+ tools []MCPTool
+ resources []MCPResource
+ serverInfo MCPServerInfo
+ closed bool
+}
+
+// NewMCPClient spawns an MCP server process and performs the handshake.
+func NewMCPClient(serverName string, config MCPServerConfig) (*MCPClient, error) {
+ cmd := exec.Command(config.Command, config.Args...)
+ if config.Cwd != "" {
+ cmd.Dir = config.Cwd
+ }
+ if len(config.Env) > 0 {
+ env := cmd.Environ()
+ for k, v := range config.Env {
+ env = append(env, k+"="+v)
+ }
+ cmd.Env = env
+ }
+
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ return nil, fmt.Errorf("creating stdin pipe: %w", err)
+ }
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ stdin.Close()
+ return nil, fmt.Errorf("creating stdout pipe: %w", err)
+ }
+
+ if err := cmd.Start(); err != nil {
+ stdin.Close()
+ return nil, fmt.Errorf("starting MCP server %q (%s): %w", serverName, config.Command, err)
+ }
+
+ client := &MCPClient{
+ serverName: serverName,
+ cmd: cmd,
+ stdin: stdin,
+ stdout: bufio.NewReader(stdout),
+ }
+ client.nextId.Store(1)
+
+ if err := client.handshake(); err != nil {
+ client.Close()
+ return nil, fmt.Errorf("MCP handshake with %q failed: %w", serverName, err)
+ }
+
+ if err := client.discoverCapabilities(); err != nil {
+ client.Close()
+ return nil, fmt.Errorf("MCP discovery for %q failed: %w", serverName, err)
+ }
+
+ log.Printf("[mcpclient] connected to MCP server %q (%s v%s), %d tools available\n",
+ serverName, client.serverInfo.Name, client.serverInfo.Version, len(client.tools))
+
+ return client, nil
+}
+
+func (c *MCPClient) handshake() error {
+ // Step 1: initialize
+ initParams := MCPInitializeParams{
+ ProtocolVersion: MCPProtocolVersion,
+ Capabilities: map[string]any{},
+ ClientInfo: MCPClientInfo{
+ Name: MCPClientName,
+ Version: MCPClientVersion,
+ },
+ }
+ var initResult MCPInitializeResult
+ if err := c.call("initialize", initParams, &initResult); err != nil {
+ return fmt.Errorf("initialize: %w", err)
+ }
+ c.serverInfo = initResult.ServerInfo
+
+ // Step 2: send initialized notification (no id, no response expected)
+ if err := c.notify("notifications/initialized"); err != nil {
+ return fmt.Errorf("initialized notification: %w", err)
+ }
+
+ return nil
+}
+
+func (c *MCPClient) discoverCapabilities() error {
+ // List tools
+ var toolsResult MCPToolsListResult
+ if err := c.call("tools/list", map[string]any{}, &toolsResult); err != nil {
+ log.Printf("[mcpclient] warning: tools/list failed for %q: %v\n", c.serverName, err)
+ } else {
+ c.tools = toolsResult.Tools
+ }
+
+ // List resources
+ var resourcesResult MCPResourcesListResult
+ if err := c.call("resources/list", map[string]any{}, &resourcesResult); err != nil {
+ log.Printf("[mcpclient] warning: resources/list failed for %q: %v\n", c.serverName, err)
+ } else {
+ c.resources = resourcesResult.Resources
+ }
+
+ return nil
+}
+
+// call sends a JSON-RPC request and waits for the response.
+func (c *MCPClient) call(method string, params any, result any) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if c.closed {
+ return fmt.Errorf("client is closed")
+ }
+
+ id := c.nextId.Add(1)
+ req := JsonRpcRequest{
+ JsonRpc: "2.0",
+ Id: id,
+ Method: method,
+ Params: params,
+ }
+
+ data, err := json.Marshal(req)
+ if err != nil {
+ return fmt.Errorf("marshaling request: %w", err)
+ }
+ data = append(data, '\n')
+
+ if _, err := c.stdin.Write(data); err != nil {
+ return fmt.Errorf("writing to stdin: %w", err)
+ }
+
+ // Read response line with timeout.
+ // On timeout we close stdin to unblock the goroutine reading stdout,
+ // then mark client as closed to prevent further use.
+ type readResult struct {
+ line []byte
+ err error
+ }
+ ch := make(chan readResult, 1)
+ go func() {
+ line, err := c.stdout.ReadBytes('\n')
+ ch <- readResult{line, err}
+ }()
+
+ select {
+ case res := <-ch:
+ if res.err != nil {
+ return fmt.Errorf("reading response: %w", res.err)
+ }
+ var resp JsonRpcResponse
+ if err := json.Unmarshal(res.line, &resp); err != nil {
+ return fmt.Errorf("parsing response: %w", err)
+ }
+ if resp.Error != nil {
+ return fmt.Errorf("RPC error %d: %s", resp.Error.Code, resp.Error.Message)
+ }
+ if result != nil && resp.Result != nil {
+ if err := json.Unmarshal(resp.Result, result); err != nil {
+ return fmt.Errorf("parsing result: %w", err)
+ }
+ }
+ return nil
+ case <-time.After(ReadTimeout):
+ // Kill the process to unblock the leaked goroutine
+ c.closed = true
+ if c.stdin != nil {
+ c.stdin.Close()
+ }
+ if c.cmd != nil && c.cmd.Process != nil {
+ c.cmd.Process.Kill()
+ }
+ return fmt.Errorf("timeout waiting for response to %q after %v - connection closed", method, ReadTimeout)
+ }
+}
+
+// notify sends a JSON-RPC notification (no id, no response expected).
+func (c *MCPClient) notify(method string) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if c.closed {
+ return fmt.Errorf("client is closed")
+ }
+
+ req := JsonRpcRequest{
+ JsonRpc: "2.0",
+ Method: method,
+ }
+ data, err := json.Marshal(req)
+ if err != nil {
+ return fmt.Errorf("marshaling notification: %w", err)
+ }
+ data = append(data, '\n')
+
+ if _, err := c.stdin.Write(data); err != nil {
+ return fmt.Errorf("writing notification: %w", err)
+ }
+
+ return nil
+}
+
+// CallTool invokes an MCP tool by name and returns the text result.
+func (c *MCPClient) CallTool(name string, arguments map[string]any) (string, error) {
+ if arguments == nil {
+ arguments = map[string]any{}
+ }
+ params := MCPToolCallParams{
+ Name: name,
+ Arguments: arguments,
+ }
+ var result MCPToolCallResult
+ if err := c.call("tools/call", params, &result); err != nil {
+ return "", fmt.Errorf("calling tool %q: %w", name, err)
+ }
+
+ // Concatenate all text content blocks
+ var text string
+ for _, content := range result.Content {
+ if content.Type == "text" {
+ if text != "" {
+ text += "\n"
+ }
+ text += content.Text
+ }
+ }
+ return text, nil
+}
+
+// ListTools returns the tools discovered from the MCP server.
+func (c *MCPClient) ListTools() []MCPTool {
+ return c.tools
+}
+
+// ListResources returns the resources discovered from the MCP server.
+func (c *MCPClient) ListResources() []MCPResource {
+ return c.resources
+}
+
+// ServerName returns the configured server name.
+func (c *MCPClient) ServerName() string {
+ return c.serverName
+}
+
+// ServerInfo returns the MCP server info from initialization.
+func (c *MCPClient) ServerInfo() MCPServerInfo {
+ return c.serverInfo
+}
+
+// IsAlive checks if the MCP server process is still running.
+func (c *MCPClient) IsAlive() bool {
+ if c.closed || c.cmd == nil || c.cmd.Process == nil {
+ return false
+ }
+ return c.cmd.ProcessState == nil
+}
+
+// Close shuts down the MCP server process.
+func (c *MCPClient) Close() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if c.closed {
+ return
+ }
+ c.closed = true
+
+ if c.stdin != nil {
+ c.stdin.Close()
+ }
+ if c.cmd != nil && c.cmd.Process != nil {
+ c.cmd.Process.Kill()
+ done := make(chan error, 1)
+ go func() { done <- c.cmd.Wait() }()
+ select {
+ case <-done:
+ case <-time.After(ShutdownTimeout):
+ log.Printf("[mcpclient] warning: MCP server %q did not terminate in time\n", c.serverName)
+ }
+ }
+
+ log.Printf("[mcpclient] closed MCP server %q\n", c.serverName)
+}
diff --git a/pkg/mcpclient/types.go b/pkg/mcpclient/types.go
new file mode 100644
index 0000000000..7f8c8b487c
--- /dev/null
+++ b/pkg/mcpclient/types.go
@@ -0,0 +1,103 @@
+// Copyright 2025, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package mcpclient
+
+import "encoding/json"
+
+// JSON-RPC 2.0 types
+
+type JsonRpcRequest struct {
+ JsonRpc string `json:"jsonrpc"`
+ Id int64 `json:"id,omitempty"`
+ Method string `json:"method"`
+ Params any `json:"params,omitempty"`
+}
+
+type JsonRpcResponse struct {
+ JsonRpc string `json:"jsonrpc"`
+ Id int64 `json:"id,omitempty"`
+ Result json.RawMessage `json:"result,omitempty"`
+ Error *JsonRpcError `json:"error,omitempty"`
+}
+
+type JsonRpcError struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ Data any `json:"data,omitempty"`
+}
+
+// MCP protocol types
+
+type MCPInitializeParams struct {
+ ProtocolVersion string `json:"protocolVersion"`
+ Capabilities map[string]any `json:"capabilities"`
+ ClientInfo MCPClientInfo `json:"clientInfo"`
+}
+
+type MCPClientInfo struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+}
+
+type MCPInitializeResult struct {
+ ProtocolVersion string `json:"protocolVersion"`
+ Capabilities any `json:"capabilities"`
+ ServerInfo MCPServerInfo `json:"serverInfo"`
+}
+
+type MCPServerInfo struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+}
+
+type MCPToolsListResult struct {
+ Tools []MCPTool `json:"tools"`
+}
+
+type MCPTool struct {
+ Name string `json:"name"`
+ Title string `json:"title,omitempty"`
+ Description string `json:"description"`
+ InputSchema map[string]any `json:"inputSchema"`
+}
+
+type MCPResourcesListResult struct {
+ Resources []MCPResource `json:"resources"`
+}
+
+type MCPResource struct {
+ URI string `json:"uri"`
+ Name string `json:"name"`
+ Title string `json:"title,omitempty"`
+ Description string `json:"description,omitempty"`
+ MimeType string `json:"mimeType,omitempty"`
+}
+
+type MCPToolCallParams struct {
+ Name string `json:"name"`
+ Arguments map[string]any `json:"arguments,omitempty"`
+}
+
+type MCPToolCallResult struct {
+ Content []MCPContent `json:"content"`
+}
+
+type MCPContent struct {
+ Type string `json:"type"`
+ Text string `json:"text,omitempty"`
+}
+
+// Configuration types
+
+type MCPConfigFile struct {
+ McpServers map[string]MCPServerConfig `json:"mcpServers"`
+}
+
+type MCPServerConfig struct {
+ Type string `json:"type,omitempty"` // "stdio" (default), "http", "sse"
+ Command string `json:"command"`
+ Args []string `json:"args,omitempty"`
+ Env map[string]string `json:"env,omitempty"`
+ Cwd string `json:"cwd,omitempty"`
+}
diff --git a/pkg/remote/conncontroller/conncontroller.go b/pkg/remote/conncontroller/conncontroller.go
index a24a789009..71e6d0f89b 100644
--- a/pkg/remote/conncontroller/conncontroller.go
+++ b/pkg/remote/conncontroller/conncontroller.go
@@ -20,24 +20,24 @@ import (
"github.com/kevinburke/ssh_config"
"github.com/skeema/knownhosts"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/genconn"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/userinput"
- "github.com/wavetermdev/waveterm/pkg/util/envutil"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "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"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/genconn"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/userinput"
+ "github.com/woveterm/wove/pkg/util/envutil"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wstore"
"golang.org/x/crypto/ssh"
"golang.org/x/mod/semver"
)
diff --git a/pkg/remote/conncontroller/connmonitor.go b/pkg/remote/conncontroller/connmonitor.go
index dc14561484..39563ec1b1 100644
--- a/pkg/remote/conncontroller/connmonitor.go
+++ b/pkg/remote/conncontroller/connmonitor.go
@@ -10,7 +10,7 @@ import (
"sync/atomic"
"time"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/panichandler"
"golang.org/x/crypto/ssh"
)
diff --git a/pkg/remote/connparse/connparse.go b/pkg/remote/connparse/connparse.go
index a4b8e99c3d..9c3bebea79 100644
--- a/pkg/remote/connparse/connparse.go
+++ b/pkg/remote/connparse/connparse.go
@@ -9,8 +9,8 @@ import (
"regexp"
"strings"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const (
diff --git a/pkg/remote/connparse/connparse_test.go b/pkg/remote/connparse/connparse_test.go
index 82a36387b7..ac12d8b903 100644
--- a/pkg/remote/connparse/connparse_test.go
+++ b/pkg/remote/connparse/connparse_test.go
@@ -3,7 +3,7 @@ package connparse_test
import (
"testing"
- "github.com/wavetermdev/waveterm/pkg/remote/connparse"
+ "github.com/woveterm/wove/pkg/remote/connparse"
)
func TestParseURI_WSHWithScheme(t *testing.T) {
diff --git a/pkg/remote/connutil.go b/pkg/remote/connutil.go
index 89d53f6ae4..d6c5d474d2 100644
--- a/pkg/remote/connutil.go
+++ b/pkg/remote/connutil.go
@@ -17,12 +17,12 @@ import (
"text/template"
"time"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/genconn"
- "github.com/wavetermdev/waveterm/pkg/util/iterfn"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/genconn"
+ "github.com/woveterm/wove/pkg/util/iterfn"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wconfig"
"golang.org/x/crypto/ssh"
)
diff --git a/pkg/remote/fileshare/fsutil/fsutil.go b/pkg/remote/fileshare/fsutil/fsutil.go
index a7efd6fdc5..8c56281911 100644
--- a/pkg/remote/fileshare/fsutil/fsutil.go
+++ b/pkg/remote/fileshare/fsutil/fsutil.go
@@ -8,10 +8,10 @@ import (
"io"
"strings"
- "github.com/wavetermdev/waveterm/pkg/remote/connparse"
- "github.com/wavetermdev/waveterm/pkg/remote/fileshare/fspath"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/remote/connparse"
+ "github.com/woveterm/wove/pkg/remote/fileshare/fspath"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
func GetParentPath(conn *connparse.Connection) string {
diff --git a/pkg/remote/fileshare/wshfs/wshfs.go b/pkg/remote/fileshare/wshfs/wshfs.go
index 80ab53e99b..efc48c03da 100644
--- a/pkg/remote/fileshare/wshfs/wshfs.go
+++ b/pkg/remote/fileshare/wshfs/wshfs.go
@@ -11,11 +11,11 @@ import (
"os"
"time"
- "github.com/wavetermdev/waveterm/pkg/remote/connparse"
- "github.com/wavetermdev/waveterm/pkg/remote/fileshare/fsutil"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/remote/connparse"
+ "github.com/woveterm/wove/pkg/remote/fileshare/fsutil"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const (
diff --git a/pkg/remote/sshclient.go b/pkg/remote/sshclient.go
index b1c9faca21..310e3ea3d5 100644
--- a/pkg/remote/sshclient.go
+++ b/pkg/remote/sshclient.go
@@ -25,16 +25,16 @@ import (
"github.com/kevinburke/ssh_config"
"github.com/skeema/knownhosts"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/secretstore"
- "github.com/wavetermdev/waveterm/pkg/trimquotes"
- "github.com/wavetermdev/waveterm/pkg/userinput"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/secretstore"
+ "github.com/woveterm/wove/pkg/trimquotes"
+ "github.com/woveterm/wove/pkg/userinput"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wconfig"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
xknownhosts "golang.org/x/crypto/ssh/knownhosts"
diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go
index c4825cf3aa..8fa95e4298 100644
--- a/pkg/schema/schema.go
+++ b/pkg/schema/schema.go
@@ -9,7 +9,7 @@ import (
"os"
"path/filepath"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavebase"
)
var schemaHandler http.Handler
diff --git a/pkg/secretstore/secretstore.go b/pkg/secretstore/secretstore.go
index e7f11bea37..8311672551 100644
--- a/pkg/secretstore/secretstore.go
+++ b/pkg/secretstore/secretstore.go
@@ -16,10 +16,10 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const (
diff --git a/pkg/service/blockservice/blockservice.go b/pkg/service/blockservice/blockservice.go
index 4770931935..51f03019ee 100644
--- a/pkg/service/blockservice/blockservice.go
+++ b/pkg/service/blockservice/blockservice.go
@@ -10,13 +10,13 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/blockcontroller"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/blockcontroller"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/tsgen/tsgenmeta"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wstore"
)
type BlockService struct{}
diff --git a/pkg/service/clientservice/clientservice.go b/pkg/service/clientservice/clientservice.go
index 094af51cb4..2976f07854 100644
--- a/pkg/service/clientservice/clientservice.go
+++ b/pkg/service/clientservice/clientservice.go
@@ -9,13 +9,13 @@ import (
"log"
"time"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wslconn"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wslconn"
+ "github.com/woveterm/wove/pkg/wstore"
)
type ClientService struct{}
diff --git a/pkg/service/objectservice/objectservice.go b/pkg/service/objectservice/objectservice.go
index 0eb228181a..926e46bcee 100644
--- a/pkg/service/objectservice/objectservice.go
+++ b/pkg/service/objectservice/objectservice.go
@@ -9,11 +9,11 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/tsgen/tsgenmeta"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
)
type ObjectService struct{}
diff --git a/pkg/service/service.go b/pkg/service/service.go
index b0ab2f9756..0ec116f54c 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -9,16 +9,16 @@ import (
"reflect"
"strings"
- "github.com/wavetermdev/waveterm/pkg/service/blockservice"
- "github.com/wavetermdev/waveterm/pkg/service/clientservice"
- "github.com/wavetermdev/waveterm/pkg/service/objectservice"
- "github.com/wavetermdev/waveterm/pkg/service/userinputservice"
- "github.com/wavetermdev/waveterm/pkg/service/windowservice"
- "github.com/wavetermdev/waveterm/pkg/service/workspaceservice"
- "github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/web/webcmd"
+ "github.com/woveterm/wove/pkg/service/blockservice"
+ "github.com/woveterm/wove/pkg/service/clientservice"
+ "github.com/woveterm/wove/pkg/service/objectservice"
+ "github.com/woveterm/wove/pkg/service/userinputservice"
+ "github.com/woveterm/wove/pkg/service/windowservice"
+ "github.com/woveterm/wove/pkg/service/workspaceservice"
+ "github.com/woveterm/wove/pkg/tsgen/tsgenmeta"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/web/webcmd"
)
var ServiceMap = map[string]any{
diff --git a/pkg/service/userinputservice/userinputservice.go b/pkg/service/userinputservice/userinputservice.go
index 7383095ba8..682b0411d8 100644
--- a/pkg/service/userinputservice/userinputservice.go
+++ b/pkg/service/userinputservice/userinputservice.go
@@ -4,7 +4,7 @@
package userinputservice
import (
- "github.com/wavetermdev/waveterm/pkg/userinput"
+ "github.com/woveterm/wove/pkg/userinput"
)
type UserInputService struct {
diff --git a/pkg/service/windowservice/windowservice.go b/pkg/service/windowservice/windowservice.go
index 81713312f3..76019993da 100644
--- a/pkg/service/windowservice/windowservice.go
+++ b/pkg/service/windowservice/windowservice.go
@@ -8,12 +8,12 @@ import (
"fmt"
"time"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/tsgen/tsgenmeta"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
)
const DefaultTimeout = 2 * time.Second
diff --git a/pkg/service/workspaceservice/workspaceservice.go b/pkg/service/workspaceservice/workspaceservice.go
index 1d7b116bdc..19aa507d2d 100644
--- a/pkg/service/workspaceservice/workspaceservice.go
+++ b/pkg/service/workspaceservice/workspaceservice.go
@@ -8,13 +8,13 @@ import (
"fmt"
"time"
- "github.com/wavetermdev/waveterm/pkg/blockcontroller"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/blockcontroller"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/tsgen/tsgenmeta"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
)
const DefaultTimeout = 2 * time.Second
diff --git a/pkg/shellexec/conninterface.go b/pkg/shellexec/conninterface.go
index f17a83c21f..dcba183c41 100644
--- a/pkg/shellexec/conninterface.go
+++ b/pkg/shellexec/conninterface.go
@@ -13,9 +13,9 @@ import (
"time"
"github.com/creack/pty"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/util/unixutil"
- "github.com/wavetermdev/waveterm/pkg/wsl"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/util/unixutil"
+ "github.com/woveterm/wove/pkg/wsl"
"golang.org/x/crypto/ssh"
)
diff --git a/pkg/shellexec/shellexec.go b/pkg/shellexec/shellexec.go
index 4850eee1b8..ef697a338d 100644
--- a/pkg/shellexec/shellexec.go
+++ b/pkg/shellexec/shellexec.go
@@ -20,18 +20,18 @@ import (
"maps"
"github.com/creack/pty"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/jobcontroller"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/util/pamparse"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
- "github.com/wavetermdev/waveterm/pkg/wslconn"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/jobcontroller"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/util/pamparse"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wslconn"
)
const DefaultGracefulKillWait = 400 * time.Millisecond
diff --git a/pkg/streamclient/stream_test.go b/pkg/streamclient/stream_test.go
index 67fb3bc057..947990a8e5 100644
--- a/pkg/streamclient/stream_test.go
+++ b/pkg/streamclient/stream_test.go
@@ -7,7 +7,7 @@ import (
"testing"
"time"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type fakeTransport struct {
diff --git a/pkg/streamclient/streambroker.go b/pkg/streamclient/streambroker.go
index 9f3ec173d9..f022f06977 100644
--- a/pkg/streamclient/streambroker.go
+++ b/pkg/streamclient/streambroker.go
@@ -6,8 +6,8 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type workItem struct {
diff --git a/pkg/streamclient/streambroker_test.go b/pkg/streamclient/streambroker_test.go
index 146816ce79..0f8784f98a 100644
--- a/pkg/streamclient/streambroker_test.go
+++ b/pkg/streamclient/streambroker_test.go
@@ -6,7 +6,7 @@ import (
"testing"
"time"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type mockRpcInterface struct {
diff --git a/pkg/streamclient/streamreader.go b/pkg/streamclient/streamreader.go
index 541d5c866d..50456e1180 100644
--- a/pkg/streamclient/streamreader.go
+++ b/pkg/streamclient/streamreader.go
@@ -7,7 +7,7 @@ import (
"sort"
"sync"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type AckSender interface {
diff --git a/pkg/streamclient/streamwriter.go b/pkg/streamclient/streamwriter.go
index 730bbf3e16..d4fea64220 100644
--- a/pkg/streamclient/streamwriter.go
+++ b/pkg/streamclient/streamwriter.go
@@ -6,7 +6,7 @@ import (
"io"
"sync"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type DataSender interface {
diff --git a/pkg/suggestion/suggestion.go b/pkg/suggestion/suggestion.go
index 4edc18cea2..3be6cb5787 100644
--- a/pkg/suggestion/suggestion.go
+++ b/pkg/suggestion/suggestion.go
@@ -16,12 +16,12 @@ import (
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/util"
- "github.com/wavetermdev/waveterm/pkg/faviconcache"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/faviconcache"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const MaxSuggestions = 50
diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go
index 7b91535bb4..deda5c0596 100644
--- a/pkg/telemetry/telemetry.go
+++ b/pkg/telemetry/telemetry.go
@@ -13,16 +13,16 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/util/daystr"
- "github.com/wavetermdev/waveterm/pkg/util/dbutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/util/daystr"
+ "github.com/woveterm/wove/pkg/util/dbutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wstore"
)
const MaxTzNameLen = 50
diff --git a/pkg/telemetry/telemetrydata/telemetrydata.go b/pkg/telemetry/telemetrydata/telemetrydata.go
index 222ebfbaed..f83d57ad01 100644
--- a/pkg/telemetry/telemetrydata/telemetrydata.go
+++ b/pkg/telemetry/telemetrydata/telemetrydata.go
@@ -10,7 +10,7 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
var ValidEventNames = map[string]bool{
diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go
index 89c782c595..961329b6f8 100644
--- a/pkg/tsgen/tsgen.go
+++ b/pkg/tsgen/tsgen.go
@@ -10,20 +10,20 @@ import (
"reflect"
"strings"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/eventbus"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/service"
- "github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
- "github.com/wavetermdev/waveterm/pkg/userinput"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/vdom"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/web/webcmd"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/eventbus"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/service"
+ "github.com/woveterm/wove/pkg/tsgen/tsgenmeta"
+ "github.com/woveterm/wove/pkg/userinput"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/vdom"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/web/webcmd"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
)
// add extra types to generate here
diff --git a/pkg/tsgen/tsgen_wshclientapi_test.go b/pkg/tsgen/tsgen_wshclientapi_test.go
index 2839a8be40..aca26dfbaf 100644
--- a/pkg/tsgen/tsgen_wshclientapi_test.go
+++ b/pkg/tsgen/tsgen_wshclientapi_test.go
@@ -8,7 +8,7 @@ import (
"strings"
"testing"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
func TestGenerateWshClientApiMethodCall_MultiArg(t *testing.T) {
diff --git a/pkg/tsgen/tsgenevent.go b/pkg/tsgen/tsgenevent.go
index 6e1c08e981..7920b6e37d 100644
--- a/pkg/tsgen/tsgenevent.go
+++ b/pkg/tsgen/tsgenevent.go
@@ -9,14 +9,14 @@ import (
"reflect"
"strconv"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/blockcontroller"
- "github.com/wavetermdev/waveterm/pkg/userinput"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/blockcontroller"
+ "github.com/woveterm/wove/pkg/userinput"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
var waveEventRType = reflect.TypeOf(wps.WaveEvent{})
diff --git a/pkg/tsgen/tsgenevent_test.go b/pkg/tsgen/tsgenevent_test.go
index 902eccda3b..a613e9e1d4 100644
--- a/pkg/tsgen/tsgenevent_test.go
+++ b/pkg/tsgen/tsgenevent_test.go
@@ -8,8 +8,8 @@ import (
"strings"
"testing"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
func TestGenerateWaveEventTypes(t *testing.T) {
diff --git a/pkg/tsunamiutil/tsunamiutil.go b/pkg/tsunamiutil/tsunamiutil.go
index 02547f9055..01d3397ca3 100644
--- a/pkg/tsunamiutil/tsunamiutil.go
+++ b/pkg/tsunamiutil/tsunamiutil.go
@@ -8,7 +8,7 @@ import (
"path/filepath"
"strings"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const DevModeCorsOrigins = "http://localhost:5173,http://localhost:5174"
diff --git a/pkg/userinput/userinput.go b/pkg/userinput/userinput.go
index e5cb8f5891..c9a0ba6519 100644
--- a/pkg/userinput/userinput.go
+++ b/pkg/userinput/userinput.go
@@ -12,11 +12,11 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/genconn"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/genconn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
)
var MainUserInputHandler = UserInputHandler{Channels: make(map[string](chan *UserInputResponse), 1)}
diff --git a/pkg/util/daystr/daystr.go b/pkg/util/daystr/daystr.go
index 71f566ff78..294bb6f5cc 100644
--- a/pkg/util/daystr/daystr.go
+++ b/pkg/util/daystr/daystr.go
@@ -9,7 +9,7 @@ import (
"strconv"
"time"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
var customDayStrRe = regexp.MustCompile(`^((?:\d{4}-\d{2}-\d{2})|today|yesterday|bom|bow)?((?:[+-]\d+[dwm])*)$`)
diff --git a/pkg/util/fileutil/fileutil.go b/pkg/util/fileutil/fileutil.go
index ce9663d521..f404eef8d0 100644
--- a/pkg/util/fileutil/fileutil.go
+++ b/pkg/util/fileutil/fileutil.go
@@ -16,7 +16,7 @@ import (
"regexp"
"strings"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavebase"
)
type ByteRangeType struct {
diff --git a/pkg/util/fileutil/readdir.go b/pkg/util/fileutil/readdir.go
index 9a4b0ba4a7..f8fa004643 100644
--- a/pkg/util/fileutil/readdir.go
+++ b/pkg/util/fileutil/readdir.go
@@ -11,8 +11,8 @@ import (
"sort"
"time"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
)
type DirEntryOut struct {
diff --git a/pkg/util/iochan/iochan.go b/pkg/util/iochan/iochan.go
index 4bb5292cf4..62bd40393d 100644
--- a/pkg/util/iochan/iochan.go
+++ b/pkg/util/iochan/iochan.go
@@ -13,10 +13,10 @@ import (
"io"
"log"
- "github.com/wavetermdev/waveterm/pkg/util/iochan/iochantypes"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/util/iochan/iochantypes"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
)
// ReaderChan reads from an io.Reader and sends the data to a channel
diff --git a/pkg/util/iochan/iochan_test.go b/pkg/util/iochan/iochan_test.go
index dd8eeb0922..bf0bf96b98 100644
--- a/pkg/util/iochan/iochan_test.go
+++ b/pkg/util/iochan/iochan_test.go
@@ -9,7 +9,7 @@ import (
"testing"
"time"
- "github.com/wavetermdev/waveterm/pkg/util/iochan"
+ "github.com/woveterm/wove/pkg/util/iochan"
)
const (
diff --git a/pkg/util/iterfn/iterfn_test.go b/pkg/util/iterfn/iterfn_test.go
index 6df11da5ed..94d7f4bb6e 100644
--- a/pkg/util/iterfn/iterfn_test.go
+++ b/pkg/util/iterfn/iterfn_test.go
@@ -5,7 +5,7 @@ import (
"slices"
"testing"
- "github.com/wavetermdev/waveterm/pkg/util/iterfn"
+ "github.com/woveterm/wove/pkg/util/iterfn"
)
func TestCollectSeqToSorted(t *testing.T) {
diff --git a/pkg/util/logutil/logutil.go b/pkg/util/logutil/logutil.go
index f023cd62cc..73cd730e24 100644
--- a/pkg/util/logutil/logutil.go
+++ b/pkg/util/logutil/logutil.go
@@ -6,7 +6,7 @@ package logutil
import (
"log"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavebase"
)
// DevPrintf logs using log.Printf only if running in dev mode
diff --git a/pkg/util/packetparser/packetparser.go b/pkg/util/packetparser/packetparser.go
index 14b9884845..e72aeb3380 100644
--- a/pkg/util/packetparser/packetparser.go
+++ b/pkg/util/packetparser/packetparser.go
@@ -10,8 +10,8 @@ import (
"io"
"log"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
type PacketParser struct {
diff --git a/pkg/util/pamparse/pamparse_test.go b/pkg/util/pamparse/pamparse_test.go
index 8e511030f8..af923a1af3 100644
--- a/pkg/util/pamparse/pamparse_test.go
+++ b/pkg/util/pamparse/pamparse_test.go
@@ -8,7 +8,7 @@ import (
"path/filepath"
"testing"
- "github.com/wavetermdev/waveterm/pkg/util/pamparse"
+ "github.com/woveterm/wove/pkg/util/pamparse"
)
// Tests influenced by https://unix.stackexchange.com/questions/748790/where-is-the-syntax-for-etc-environment-documented
diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go
index e6f6c21f38..0af7e9c027 100644
--- a/pkg/util/shellutil/shellutil.go
+++ b/pkg/util/shellutil/shellutil.go
@@ -18,12 +18,12 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/util/envutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/utilds"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/util/envutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/utilds"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
)
var (
diff --git a/pkg/util/shellutil/tokenswap.go b/pkg/util/shellutil/tokenswap.go
index 302bfa59b8..b4d884f660 100644
--- a/pkg/util/shellutil/tokenswap.go
+++ b/pkg/util/shellutil/tokenswap.go
@@ -10,7 +10,7 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
var tokenSwapMap map[string]*TokenSwapEntry = make(map[string]*TokenSwapEntry)
diff --git a/pkg/util/sigutil/sigusr1_notwindows.go b/pkg/util/sigutil/sigusr1_notwindows.go
index df0cb8babe..d80b57f94f 100644
--- a/pkg/util/sigutil/sigusr1_notwindows.go
+++ b/pkg/util/sigutil/sigusr1_notwindows.go
@@ -11,8 +11,8 @@ import (
"os/signal"
"syscall"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
const DumpFilePath = "/tmp/waveterm-usr1-dump.log"
diff --git a/pkg/util/sigutil/sigutil.go b/pkg/util/sigutil/sigutil.go
index 434ae91d9b..0e61911473 100644
--- a/pkg/util/sigutil/sigutil.go
+++ b/pkg/util/sigutil/sigutil.go
@@ -9,7 +9,7 @@ import (
"os/signal"
"syscall"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/panichandler"
)
func InstallShutdownSignalHandlers(doShutdown func(string)) {
diff --git a/pkg/vdom/vdom.go b/pkg/vdom/vdom.go
index 4f0993acfa..aca6fa290d 100644
--- a/pkg/vdom/vdom.go
+++ b/pkg/vdom/vdom.go
@@ -13,7 +13,7 @@ import (
"strings"
"unicode"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
// ReactNode types = nil | string | Elem
diff --git a/pkg/vdom/vdom_html.go b/pkg/vdom/vdom_html.go
index 527e168e6b..8f3036b149 100644
--- a/pkg/vdom/vdom_html.go
+++ b/pkg/vdom/vdom_html.go
@@ -11,7 +11,7 @@ import (
"strings"
"github.com/wavetermdev/htmltoken"
- "github.com/wavetermdev/waveterm/pkg/vdom/cssparser"
+ "github.com/woveterm/wove/pkg/vdom/cssparser"
)
// can tokenize and bind HTML to Elems
diff --git a/pkg/vdom/vdom_root.go b/pkg/vdom/vdom_root.go
index 41f8469d75..2704448efc 100644
--- a/pkg/vdom/vdom_root.go
+++ b/pkg/vdom/vdom_root.go
@@ -12,7 +12,7 @@ import (
"strings"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
const (
diff --git a/pkg/vdom/vdom_test.go b/pkg/vdom/vdom_test.go
index db56a4415a..589f6e1494 100644
--- a/pkg/vdom/vdom_test.go
+++ b/pkg/vdom/vdom_test.go
@@ -8,7 +8,7 @@ import (
"reflect"
"testing"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
type renderContextKeyType struct{}
diff --git a/pkg/vdom/vdom_types.go b/pkg/vdom/vdom_types.go
index 9ff5a4157e..190ec7b498 100644
--- a/pkg/vdom/vdom_types.go
+++ b/pkg/vdom/vdom_types.go
@@ -6,7 +6,7 @@ package vdom
import (
"time"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/waveobj"
)
const TextTag = "#text"
diff --git a/pkg/waveai/anthropicbackend.go b/pkg/waveai/anthropicbackend.go
index 05a605bad9..a8be0cce91 100644
--- a/pkg/waveai/anthropicbackend.go
+++ b/pkg/waveai/anthropicbackend.go
@@ -14,8 +14,8 @@ import (
"net/url"
"strings"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type AnthropicBackend struct{}
diff --git a/pkg/waveai/cloudbackend.go b/pkg/waveai/cloudbackend.go
index f1148e591e..8e131abd99 100644
--- a/pkg/waveai/cloudbackend.go
+++ b/pkg/waveai/cloudbackend.go
@@ -12,9 +12,9 @@ import (
"time"
"github.com/gorilla/websocket"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wcloud"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wcloud"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type WaveAICloudBackend struct{}
diff --git a/pkg/waveai/googlebackend.go b/pkg/waveai/googlebackend.go
index 9282bc5f87..0e5c7cc1db 100644
--- a/pkg/waveai/googlebackend.go
+++ b/pkg/waveai/googlebackend.go
@@ -11,7 +11,7 @@ import (
"net/url"
"github.com/google/generative-ai-go/genai"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)
diff --git a/pkg/waveai/openaibackend.go b/pkg/waveai/openaibackend.go
index 4001a3a670..5778c9f483 100644
--- a/pkg/waveai/openaibackend.go
+++ b/pkg/waveai/openaibackend.go
@@ -14,8 +14,8 @@ import (
"strings"
openaiapi "github.com/sashabaranov/go-openai"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type OpenAIBackend struct{}
diff --git a/pkg/waveai/perplexitybackend.go b/pkg/waveai/perplexitybackend.go
index e24481d417..c52c8f513f 100644
--- a/pkg/waveai/perplexitybackend.go
+++ b/pkg/waveai/perplexitybackend.go
@@ -14,8 +14,8 @@ import (
"net/url"
"strings"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type PerplexityBackend struct{}
diff --git a/pkg/waveai/waveai.go b/pkg/waveai/waveai.go
index 4d012e968a..9e7807cc2b 100644
--- a/pkg/waveai/waveai.go
+++ b/pkg/waveai/waveai.go
@@ -9,9 +9,9 @@ import (
"net/url"
"strings"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const WaveAIPacketstr = "waveai"
diff --git a/pkg/waveapp/streamingresp.go b/pkg/waveapp/streamingresp.go
index 7963c39aef..06c929bd84 100644
--- a/pkg/waveapp/streamingresp.go
+++ b/pkg/waveapp/streamingresp.go
@@ -7,7 +7,7 @@ import (
"bytes"
"net/http"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const maxChunkSize = 64 * 1024 // 64KB maximum chunk size
diff --git a/pkg/waveapp/waveapp.go b/pkg/waveapp/waveapp.go
index 1ef651bbfa..00d55998a1 100644
--- a/pkg/waveapp/waveapp.go
+++ b/pkg/waveapp/waveapp.go
@@ -19,12 +19,12 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
- "github.com/wavetermdev/waveterm/pkg/vdom"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/vdom"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
type AppOpts struct {
diff --git a/pkg/waveapp/waveappserverimpl.go b/pkg/waveapp/waveappserverimpl.go
index 5de842b1f2..07180d9cba 100644
--- a/pkg/waveapp/waveappserverimpl.go
+++ b/pkg/waveapp/waveappserverimpl.go
@@ -10,9 +10,9 @@ import (
"log"
"net/http"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/vdom"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/vdom"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type WaveAppServerImpl struct {
diff --git a/pkg/waveappstore/waveappstore.go b/pkg/waveappstore/waveappstore.go
index 1f4a88d7ba..24e5459783 100644
--- a/pkg/waveappstore/waveappstore.go
+++ b/pkg/waveappstore/waveappstore.go
@@ -12,11 +12,11 @@ import (
"regexp"
"strings"
- "github.com/wavetermdev/waveterm/pkg/secretstore"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/waveapputil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/secretstore"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/waveapputil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const (
diff --git a/pkg/waveapputil/waveapputil.go b/pkg/waveapputil/waveapputil.go
index 7efb1bfdc9..25ef38d949 100644
--- a/pkg/waveapputil/waveapputil.go
+++ b/pkg/waveapputil/waveapputil.go
@@ -11,9 +11,9 @@ import (
"path/filepath"
"runtime"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/tsunami/build"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/tsunami/build"
)
const DefaultTsunamiSdkVersion = "v0.12.4"
diff --git a/pkg/wavebase/wavebase.go b/pkg/wavebase/wavebase.go
index f24a226745..b5facbabdb 100644
--- a/pkg/wavebase/wavebase.go
+++ b/pkg/wavebase/wavebase.go
@@ -18,7 +18,7 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
// set by main-server.go
diff --git a/pkg/waveobj/waveobj.go b/pkg/waveobj/waveobj.go
index d4068a4540..b2d1efc13f 100644
--- a/pkg/waveobj/waveobj.go
+++ b/pkg/waveobj/waveobj.go
@@ -13,7 +13,7 @@ import (
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
const (
diff --git a/pkg/wcloud/wcloud.go b/pkg/wcloud/wcloud.go
index 3b96df838b..7ab7edf71f 100644
--- a/pkg/wcloud/wcloud.go
+++ b/pkg/wcloud/wcloud.go
@@ -17,10 +17,10 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/util/daystr"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/util/daystr"
+ "github.com/woveterm/wove/pkg/wavebase"
)
const WCloudEndpoint = "https://api.waveterm.dev/central"
diff --git a/pkg/wcloud/wclouddata.go b/pkg/wcloud/wclouddata.go
index 96ab409cff..9f3232c8b0 100644
--- a/pkg/wcloud/wclouddata.go
+++ b/pkg/wcloud/wclouddata.go
@@ -4,7 +4,7 @@
package wcloud
import (
- "github.com/wavetermdev/waveterm/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry"
)
type NoTelemetryInputType struct {
diff --git a/pkg/wconfig/defaultconfig/waveai.json b/pkg/wconfig/defaultconfig/waveai.json
index eba31f5030..f2737f0e2a 100644
--- a/pkg/wconfig/defaultconfig/waveai.json
+++ b/pkg/wconfig/defaultconfig/waveai.json
@@ -39,5 +39,117 @@
"ai:capabilities": ["tools", "images", "pdfs"],
"waveai:premium": true,
"ai:switchcompat": ["wavecloud"]
+ },
+ "byok@claude-sonnet": {
+ "display:name": "Claude Sonnet 4",
+ "display:order": 10,
+ "display:icon": "message",
+ "display:description": "Anthropic Claude Sonnet 4\nFast, capable, great for coding",
+ "ai:provider": "custom",
+ "ai:apitype": "anthropic-messages",
+ "ai:model": "claude-sonnet-4-6",
+ "ai:endpoint": "https://api.anthropic.com/v1/messages",
+ "ai:apitokensecretname": "anthropic_api_key",
+ "ai:capabilities": ["tools", "images", "pdfs"],
+ "ai:switchcompat": ["byok-anthropic"]
+ },
+ "byok@claude-opus": {
+ "display:name": "Claude Opus 4.6",
+ "display:order": 11,
+ "display:icon": "brain",
+ "display:description": "Anthropic Claude Opus 4.6\nMost capable, deep reasoning",
+ "ai:provider": "custom",
+ "ai:apitype": "anthropic-messages",
+ "ai:model": "claude-opus-4-6",
+ "ai:endpoint": "https://api.anthropic.com/v1/messages",
+ "ai:apitokensecretname": "anthropic_api_key",
+ "ai:capabilities": ["tools", "images", "pdfs"],
+ "ai:switchcompat": ["byok-anthropic"]
+ },
+ "byok@gpt5-mini": {
+ "display:name": "GPT-5 Mini",
+ "display:order": 20,
+ "display:icon": "bolt",
+ "display:description": "OpenAI GPT-5 Mini (BYOK)\nFast and cheap",
+ "ai:provider": "openai",
+ "ai:apitype": "openai-responses",
+ "ai:model": "gpt-5-mini",
+ "ai:apitokensecretname": "openai_api_key",
+ "ai:capabilities": ["tools", "images", "pdfs"],
+ "ai:switchcompat": ["byok-openai"]
+ },
+ "byok@gpt5": {
+ "display:name": "GPT-5.1",
+ "display:order": 21,
+ "display:icon": "sparkles",
+ "display:description": "OpenAI GPT-5.1 (BYOK)\nMost capable OpenAI model",
+ "ai:provider": "openai",
+ "ai:apitype": "openai-responses",
+ "ai:model": "gpt-5.1",
+ "ai:apitokensecretname": "openai_api_key",
+ "ai:capabilities": ["tools", "images", "pdfs"],
+ "ai:switchcompat": ["byok-openai"]
+ },
+ "byok@gemini-flash": {
+ "display:name": "Gemini 3.0 Flash",
+ "display:order": 30,
+ "display:icon": "bolt",
+ "display:description": "Google Gemini Flash\nFast and free tier available",
+ "ai:provider": "google",
+ "ai:apitype": "google-gemini",
+ "ai:model": "gemini-3.0-flash",
+ "ai:apitokensecretname": "google_ai_key",
+ "ai:capabilities": ["tools", "images", "pdfs"],
+ "ai:switchcompat": ["byok-google"]
+ },
+ "byok@gemini-pro": {
+ "display:name": "Gemini 3.0 Pro",
+ "display:order": 31,
+ "display:icon": "brain",
+ "display:description": "Google Gemini Pro\nMost capable Google model",
+ "ai:provider": "google",
+ "ai:apitype": "google-gemini",
+ "ai:model": "gemini-3.0-pro",
+ "ai:apitokensecretname": "google_ai_key",
+ "ai:capabilities": ["tools", "images", "pdfs"],
+ "ai:switchcompat": ["byok-google"]
+ },
+ "byok@minimax": {
+ "display:name": "MiniMax M2.7",
+ "display:order": 35,
+ "display:icon": "bolt",
+ "display:description": "MiniMax M2.7\nFast, cheap, 97% skill adherence",
+ "ai:provider": "custom",
+ "ai:apitype": "openai-chat",
+ "ai:model": "MiniMax-M2.7",
+ "ai:endpoint": "https://api.minimax.io/v1/chat/completions",
+ "ai:apitokensecretname": "minimax_api_key",
+ "ai:capabilities": ["tools"],
+ "ai:switchcompat": ["byok-minimax"]
+ },
+ "byok@ollama": {
+ "display:name": "Ollama (Local)",
+ "display:order": 40,
+ "display:icon": "server",
+ "display:description": "Local model via Ollama\nNo API key needed, fully private",
+ "ai:provider": "custom",
+ "ai:apitype": "openai-chat",
+ "ai:model": "llama3.3",
+ "ai:endpoint": "http://localhost:11434/v1",
+ "ai:capabilities": ["tools"],
+ "ai:switchcompat": ["byok-local"]
+ },
+ "byok@openrouter": {
+ "display:name": "OpenRouter",
+ "display:order": 50,
+ "display:icon": "route",
+ "display:description": "OpenRouter — access any model\nClaude, GPT, Gemini, Llama via one API",
+ "ai:provider": "openrouter",
+ "ai:apitype": "openai-chat",
+ "ai:model": "anthropic/claude-sonnet-4",
+ "ai:endpoint": "https://openrouter.ai/api/v1",
+ "ai:apitokensecretname": "openrouter_api_key",
+ "ai:capabilities": ["tools"],
+ "ai:switchcompat": ["byok-openrouter"]
}
}
diff --git a/pkg/wconfig/defaultconfig/widgets.json b/pkg/wconfig/defaultconfig/widgets.json
index 97a3d26c10..58e0b069df 100644
--- a/pkg/wconfig/defaultconfig/widgets.json
+++ b/pkg/wconfig/defaultconfig/widgets.json
@@ -50,5 +50,16 @@
"view": "sysinfo"
}
}
+ },
+ "defwidget@mcpclient": {
+ "display:order": 0,
+ "icon": "plug",
+ "label": "mcp client",
+ "description": "MCP Client - connect to Model Context Protocol servers",
+ "blockdef": {
+ "meta": {
+ "view": "mcpclient"
+ }
+ }
}
}
diff --git a/pkg/wconfig/filewatcher.go b/pkg/wconfig/filewatcher.go
index d0d4d2e9c1..4ec444ed02 100644
--- a/pkg/wconfig/filewatcher.go
+++ b/pkg/wconfig/filewatcher.go
@@ -11,9 +11,9 @@ import (
"sync"
"github.com/fsnotify/fsnotify"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wps"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wps"
)
var instance *Watcher
diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go
index c52012803d..dfacee6f35 100644
--- a/pkg/wconfig/settingsconfig.go
+++ b/pkg/wconfig/settingsconfig.go
@@ -16,11 +16,11 @@ import (
"strings"
"sync"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig/defaultconfig"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig/defaultconfig"
)
const SettingsFile = "settings.json"
diff --git a/pkg/wcore/badge.go b/pkg/wcore/badge.go
index a60ecb8fc1..0dba92734b 100644
--- a/pkg/wcore/badge.go
+++ b/pkg/wcore/badge.go
@@ -7,11 +7,11 @@ import (
"log"
"sync"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
)
// BadgeStore is an in-memory store for transient badges.
diff --git a/pkg/wcore/block.go b/pkg/wcore/block.go
index d9f484df86..7c1e112c00 100644
--- a/pkg/wcore/block.go
+++ b/pkg/wcore/block.go
@@ -10,15 +10,15 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wstore"
)
func CreateSubBlock(ctx context.Context, blockId string, blockDef *waveobj.BlockDef) (*waveobj.Block, error) {
diff --git a/pkg/wcore/layout.go b/pkg/wcore/layout.go
index 173b8218e4..c88a90fbee 100644
--- a/pkg/wcore/layout.go
+++ b/pkg/wcore/layout.go
@@ -10,8 +10,8 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wstore"
)
const (
@@ -48,7 +48,7 @@ func GetStarterLayout() PortableLayout {
{IndexArr: []int{1, 1}, BlockDef: &waveobj.BlockDef{
Meta: waveobj.MetaMapType{
waveobj.MetaKey_View: "web",
- waveobj.MetaKey_Url: "https://github.com/wavetermdev/waveterm",
+ waveobj.MetaKey_Url: "https://github.com/woveterm/wove",
},
}},
{IndexArr: []int{1, 2}, BlockDef: &waveobj.BlockDef{
diff --git a/pkg/wcore/wcore.go b/pkg/wcore/wcore.go
index d82aa67d3a..4fe6d79a60 100644
--- a/pkg/wcore/wcore.go
+++ b/pkg/wcore/wcore.go
@@ -17,12 +17,12 @@ import (
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wavejwt"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wcloud"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wavejwt"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcloud"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wstore"
)
// the wcore package coordinates actions across the storage layer
diff --git a/pkg/wcore/window.go b/pkg/wcore/window.go
index 7819defd62..324c7b10fd 100644
--- a/pkg/wcore/window.go
+++ b/pkg/wcore/window.go
@@ -9,13 +9,13 @@ import (
"log"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/eventbus"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "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"
+ "github.com/woveterm/wove/pkg/eventbus"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wstore"
)
func SwitchWorkspace(ctx context.Context, windowId string, workspaceId string) (*waveobj.Workspace, error) {
diff --git a/pkg/wcore/workspace.go b/pkg/wcore/workspace.go
index b070d31107..f4dc926fe2 100644
--- a/pkg/wcore/workspace.go
+++ b/pkg/wcore/workspace.go
@@ -12,15 +12,15 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/eventbus"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/eventbus"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wstore"
)
var WorkspaceColors = [...]string{
diff --git a/pkg/web/sse/ssehandler.go b/pkg/web/sse/ssehandler.go
index 4012d716a4..1804e91097 100644
--- a/pkg/web/sse/ssehandler.go
+++ b/pkg/web/sse/ssehandler.go
@@ -12,7 +12,7 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/utilds"
+ "github.com/woveterm/wove/pkg/utilds"
)
// see /aiprompts/usechat-streamingproto.md for protocol
diff --git a/pkg/web/web.go b/pkg/web/web.go
index 106db981e4..35bb36be98 100644
--- a/pkg/web/web.go
+++ b/pkg/web/web.go
@@ -20,17 +20,20 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
- "github.com/wavetermdev/waveterm/pkg/aiusechat"
- "github.com/wavetermdev/waveterm/pkg/authkey"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs"
- "github.com/wavetermdev/waveterm/pkg/schema"
- "github.com/wavetermdev/waveterm/pkg/service"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/aiusechat"
+ "github.com/woveterm/wove/pkg/aiusechat/aiplan"
+ "github.com/woveterm/wove/pkg/aiusechat/sessionhistory"
+ "github.com/woveterm/wove/pkg/authkey"
+ "github.com/woveterm/wove/pkg/mcpclient"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote/fileshare/wshfs"
+ "github.com/woveterm/wove/pkg/schema"
+ "github.com/woveterm/wove/pkg/service"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
)
type WebFnType = func(http.ResponseWriter, *http.Request)
@@ -459,6 +462,16 @@ func RunWebServer(listener net.Listener) {
waveRouter.HandleFunc("/wave/service", WebFnWrap(WebFnOpts{JsonErrors: true}, handleService))
waveRouter.HandleFunc("/wave/aichat", WebFnWrap(WebFnOpts{JsonErrors: true, AllowCaching: false}, aiusechat.WaveAIGetChatHandler))
+ // Session history and plan endpoints
+ waveRouter.HandleFunc("/wave/session-history", WebFnWrap(WebFnOpts{JsonErrors: true}, sessionhistory.HandleSessionHistory))
+ waveRouter.HandleFunc("/wave/plan/status", WebFnWrap(WebFnOpts{JsonErrors: true}, aiplan.HandlePlanStatus))
+ waveRouter.HandleFunc("/wave/plan/delete", WebFnWrap(WebFnOpts{JsonErrors: true}, aiplan.HandlePlanDelete))
+
+ // MCP client API endpoints
+ waveRouter.HandleFunc("/wave/mcp/status", WebFnWrap(WebFnOpts{JsonErrors: true}, mcpclient.HandleMCPStatus))
+ waveRouter.HandleFunc("/wave/mcp/call", WebFnWrap(WebFnOpts{JsonErrors: true}, mcpclient.HandleMCPCall))
+ waveRouter.HandleFunc("/wave/mcp/calllog", WebFnWrap(WebFnOpts{JsonErrors: true}, mcpclient.HandleMCPCallLog))
+
vdomRouter := mux.NewRouter()
vdomRouter.HandleFunc("/vdom/{uuid}/{path:.*}", WebFnWrap(WebFnOpts{AllowCaching: true}, handleVDom))
diff --git a/pkg/web/webcmd/webcmd.go b/pkg/web/webcmd/webcmd.go
index b86934ce7a..3d4a1a2cd4 100644
--- a/pkg/web/webcmd/webcmd.go
+++ b/pkg/web/webcmd/webcmd.go
@@ -7,9 +7,9 @@ import (
"fmt"
"reflect"
- "github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/tsgen/tsgenmeta"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const (
diff --git a/pkg/web/webvdomproto.go b/pkg/web/webvdomproto.go
index 8df1f46081..9022d68431 100644
--- a/pkg/web/webvdomproto.go
+++ b/pkg/web/webvdomproto.go
@@ -10,10 +10,10 @@ import (
"net/http"
"strings"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshserver"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshrpc/wshserver"
+ "github.com/woveterm/wove/pkg/wshutil"
)
// Add the new handler function
diff --git a/pkg/web/ws.go b/pkg/web/ws.go
index ddd84fe7ae..f6ba47f5e5 100644
--- a/pkg/web/ws.go
+++ b/pkg/web/ws.go
@@ -15,12 +15,12 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
- "github.com/wavetermdev/waveterm/pkg/authkey"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/eventbus"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/web/webcmd"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/authkey"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/eventbus"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/web/webcmd"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const wsReadWaitTimeout = 15 * time.Second
diff --git a/pkg/wps/wps.go b/pkg/wps/wps.go
index f36fba10ca..470f5f3243 100644
--- a/pkg/wps/wps.go
+++ b/pkg/wps/wps.go
@@ -8,8 +8,8 @@ import (
"strings"
"sync"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
)
// this broker interface is mostly generic
diff --git a/pkg/wps/wpstypes.go b/pkg/wps/wpstypes.go
index 0077ec9d5e..fca7d0d2f8 100644
--- a/pkg/wps/wpstypes.go
+++ b/pkg/wps/wpstypes.go
@@ -4,7 +4,7 @@
package wps
import (
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
// IMPORTANT: When adding a new event constant, you MUST also:
diff --git a/pkg/wshrpc/wshclient/barerpcclient.go b/pkg/wshrpc/wshclient/barerpcclient.go
index d430266372..d5192bb068 100644
--- a/pkg/wshrpc/wshclient/barerpcclient.go
+++ b/pkg/wshrpc/wshclient/barerpcclient.go
@@ -8,9 +8,9 @@ import (
"sync"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
)
type WshServer struct{}
diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go
index 67ef8669ec..c62ef6d030 100644
--- a/pkg/wshrpc/wshclient/wshclient.go
+++ b/pkg/wshrpc/wshclient/wshclient.go
@@ -6,15 +6,15 @@
package wshclient
import (
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/vdom"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/vdom"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
)
// command "activity", wshserver.ActivityCommand
@@ -997,6 +997,12 @@ func WaveAIGetToolDiffCommand(w *wshutil.WshRpc, data wshrpc.CommandWaveAIGetToo
return resp, err
}
+// command "waveaisessionreadapprove", wshserver.WaveAISessionReadApproveCommand
+func WaveAISessionReadApproveCommand(w *wshutil.WshRpc, data wshrpc.CommandWaveAISessionReadApproveData, opts *wshrpc.RpcOpts) error {
+ _, err := sendRpcRequestCallHelper[any](w, "waveaisessionreadapprove", data, opts)
+ return err
+}
+
// command "waveaitoolapprove", wshserver.WaveAIToolApproveCommand
func WaveAIToolApproveCommand(w *wshutil.WshRpc, data wshrpc.CommandWaveAIToolApproveData, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "waveaitoolapprove", data, opts)
diff --git a/pkg/wshrpc/wshclient/wshclientutil.go b/pkg/wshrpc/wshclient/wshclientutil.go
index 52d311c0a9..eeaf996232 100644
--- a/pkg/wshrpc/wshclient/wshclientutil.go
+++ b/pkg/wshrpc/wshclient/wshclientutil.go
@@ -7,10 +7,10 @@ import (
"context"
"errors"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
)
func sendRpcRequestCallHelper[T any](w *wshutil.WshRpc, command string, data interface{}, opts *wshrpc.RpcOpts) (T, error) {
diff --git a/pkg/wshrpc/wshremote/sysinfo.go b/pkg/wshrpc/wshremote/sysinfo.go
index c573c4d9d1..0819285ef1 100644
--- a/pkg/wshrpc/wshremote/sysinfo.go
+++ b/pkg/wshrpc/wshremote/sysinfo.go
@@ -10,10 +10,10 @@ import (
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/mem"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const BYTES_PER_GB = 1073741824
diff --git a/pkg/wshrpc/wshremote/wshremote.go b/pkg/wshrpc/wshremote/wshremote.go
index 35dc92d5bb..fd3eb9e36a 100644
--- a/pkg/wshrpc/wshremote/wshremote.go
+++ b/pkg/wshrpc/wshremote/wshremote.go
@@ -14,15 +14,15 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/suggestion"
- "github.com/wavetermdev/waveterm/pkg/util/unixutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/suggestion"
+ "github.com/woveterm/wove/pkg/util/unixutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
type JobManagerConnection struct {
diff --git a/pkg/wshrpc/wshremote/wshremote_file.go b/pkg/wshrpc/wshremote/wshremote_file.go
index d796154f48..73b4258cc7 100644
--- a/pkg/wshrpc/wshremote/wshremote_file.go
+++ b/pkg/wshrpc/wshremote/wshremote_file.go
@@ -16,16 +16,16 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote/connparse"
- "github.com/wavetermdev/waveterm/pkg/remote/fileshare/fsutil"
- "github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote/connparse"
+ "github.com/woveterm/wove/pkg/remote/fileshare/fsutil"
+ "github.com/woveterm/wove/pkg/remote/fileshare/wshfs"
+ "github.com/woveterm/wove/pkg/util/fileutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const RemoteFileTransferSizeLimit = 32 * 1024 * 1024
diff --git a/pkg/wshrpc/wshremote/wshremote_job.go b/pkg/wshrpc/wshremote/wshremote_job.go
index b357116427..a82bfdf51b 100644
--- a/pkg/wshrpc/wshremote/wshremote_job.go
+++ b/pkg/wshrpc/wshremote/wshremote_job.go
@@ -17,10 +17,10 @@ import (
"time"
"github.com/shirou/gopsutil/v4/process"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc/wshclient"
+ "github.com/woveterm/wove/pkg/wshutil"
)
func isProcessRunning(pid int, pidStartTs int64) (*process.Process, error) {
diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go
index 2fee3e392e..8066d1d134 100644
--- a/pkg/wshrpc/wshrpctypes.go
+++ b/pkg/wshrpc/wshrpctypes.go
@@ -10,13 +10,13 @@ import (
"encoding/json"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/vdom"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/vdom"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
)
type RespOrErrorUnion[T any] struct {
@@ -157,6 +157,7 @@ type WshRpcInterface interface {
GetWaveAIChatCommand(ctx context.Context, data CommandGetWaveAIChatData) (*uctypes.UIChat, error)
GetWaveAIRateLimitCommand(ctx context.Context) (*uctypes.RateLimitInfo, error)
WaveAIToolApproveCommand(ctx context.Context, data CommandWaveAIToolApproveData) error
+ WaveAISessionReadApproveCommand(ctx context.Context, data CommandWaveAISessionReadApproveData) error
WaveAIAddContextCommand(ctx context.Context, data CommandWaveAIAddContextData) error
WaveAIGetToolDiffCommand(ctx context.Context, data CommandWaveAIGetToolDiffData) (*CommandWaveAIGetToolDiffRtnData, error)
@@ -470,8 +471,12 @@ type ConnStatus struct {
}
type WebSelectorOpts struct {
- All bool `json:"all,omitempty"`
- Inner bool `json:"inner,omitempty"`
+ All bool `json:"all,omitempty"`
+ Inner bool `json:"inner,omitempty"`
+ InnerText bool `json:"innertext,omitempty"`
+ Reload bool `json:"reload,omitempty"`
+ ExecJs string `json:"execjs,omitempty"`
+ Highlight bool `json:"highlight,omitempty"`
}
type CommandWebSelectorData struct {
@@ -548,6 +553,10 @@ type CommandWaveAIToolApproveData struct {
Approval string `json:"approval,omitempty"`
}
+type CommandWaveAISessionReadApproveData struct {
+ Path string `json:"path"`
+}
+
type AIAttachedFile struct {
Name string `json:"name"`
Type string `json:"type"`
diff --git a/pkg/wshrpc/wshrpctypes_file.go b/pkg/wshrpc/wshrpctypes_file.go
index a43a68bc2e..d98b76773c 100644
--- a/pkg/wshrpc/wshrpctypes_file.go
+++ b/pkg/wshrpc/wshrpctypes_file.go
@@ -8,7 +8,7 @@ import (
"context"
"os"
- "github.com/wavetermdev/waveterm/pkg/ijson"
+ "github.com/woveterm/wove/pkg/ijson"
)
type WshRpcFileInterface interface {
diff --git a/pkg/wshrpc/wshserver/resolvers.go b/pkg/wshrpc/wshserver/resolvers.go
index 3a6a33375c..707293f21f 100644
--- a/pkg/wshrpc/wshserver/resolvers.go
+++ b/pkg/wshrpc/wshserver/resolvers.go
@@ -11,9 +11,9 @@ import (
"strings"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wstore"
)
const (
diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go
index b2c533ff48..4668e56320 100644
--- a/pkg/wshrpc/wshserver/wshserver.go
+++ b/pkg/wshrpc/wshserver/wshserver.go
@@ -21,44 +21,44 @@ import (
"time"
"github.com/skratchdot/open-golang/open"
- "github.com/wavetermdev/waveterm/pkg/aiusechat"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/blockcontroller"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/buildercontroller"
- "github.com/wavetermdev/waveterm/pkg/filebackup"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/genconn"
- "github.com/wavetermdev/waveterm/pkg/jobcontroller"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs"
- "github.com/wavetermdev/waveterm/pkg/secretstore"
- "github.com/wavetermdev/waveterm/pkg/suggestion"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/util/envutil"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveai"
- "github.com/wavetermdev/waveterm/pkg/waveappstore"
- "github.com/wavetermdev/waveterm/pkg/waveapputil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wavejwt"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wcloud"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wcore"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
- "github.com/wavetermdev/waveterm/pkg/wsl"
- "github.com/wavetermdev/waveterm/pkg/wslconn"
- "github.com/wavetermdev/waveterm/pkg/wstore"
- "github.com/wavetermdev/waveterm/tsunami/build"
+ "github.com/woveterm/wove/pkg/aiusechat"
+ "github.com/woveterm/wove/pkg/aiusechat/chatstore"
+ "github.com/woveterm/wove/pkg/aiusechat/uctypes"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/blockcontroller"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/buildercontroller"
+ "github.com/woveterm/wove/pkg/filebackup"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/genconn"
+ "github.com/woveterm/wove/pkg/jobcontroller"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/remote/fileshare/wshfs"
+ "github.com/woveterm/wove/pkg/secretstore"
+ "github.com/woveterm/wove/pkg/suggestion"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/util/envutil"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveai"
+ "github.com/woveterm/wove/pkg/waveappstore"
+ "github.com/woveterm/wove/pkg/waveapputil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavejwt"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wcloud"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wcore"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wsl"
+ "github.com/woveterm/wove/pkg/wslconn"
+ "github.com/woveterm/wove/pkg/wstore"
+ "github.com/woveterm/wove/tsunami/build"
)
var InvalidWslDistroNames = []string{"docker-desktop", "docker-desktop-data"}
@@ -1308,6 +1308,14 @@ func (ws *WshServer) WaveAIToolApproveCommand(ctx context.Context, data wshrpc.C
return aiusechat.UpdateToolApproval(data.ToolCallId, data.Approval)
}
+func (ws *WshServer) WaveAISessionReadApproveCommand(ctx context.Context, data wshrpc.CommandWaveAISessionReadApproveData) error {
+ if data.Path == "" {
+ return fmt.Errorf("path is required")
+ }
+ aiusechat.AddSessionReadApproval(data.Path)
+ return nil
+}
+
func (ws *WshServer) WaveAIGetToolDiffCommand(ctx context.Context, data wshrpc.CommandWaveAIGetToolDiffData) (*wshrpc.CommandWaveAIGetToolDiffRtnData, error) {
originalContent, modifiedContent, err := aiusechat.CreateWriteTextFileDiff(ctx, data.ChatId, data.ToolCallId)
if err != nil {
diff --git a/pkg/wshrpc/wshserver/wshserverutil.go b/pkg/wshrpc/wshserver/wshserverutil.go
index c4e3f3543d..6a93051297 100644
--- a/pkg/wshrpc/wshserver/wshserverutil.go
+++ b/pkg/wshrpc/wshserver/wshserverutil.go
@@ -6,8 +6,8 @@ package wshserver
import (
"sync"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
)
const (
diff --git a/pkg/wshutil/wshadapter.go b/pkg/wshutil/wshadapter.go
index db7f7c20b6..4ab5b8a8b4 100644
--- a/pkg/wshutil/wshadapter.go
+++ b/pkg/wshutil/wshadapter.go
@@ -8,9 +8,9 @@ import (
"reflect"
"strings"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
var WshCommandDeclMap = wshrpc.GenerateWshCommandDeclMap()
diff --git a/pkg/wshutil/wshcmdreader.go b/pkg/wshutil/wshcmdreader.go
index 60007fe0e5..0781d019c8 100644
--- a/pkg/wshutil/wshcmdreader.go
+++ b/pkg/wshutil/wshcmdreader.go
@@ -9,7 +9,7 @@ import (
"io"
"sync"
- "github.com/wavetermdev/waveterm/pkg/baseds"
+ "github.com/woveterm/wove/pkg/baseds"
)
const (
diff --git a/pkg/wshutil/wshevent.go b/pkg/wshutil/wshevent.go
index 38057936a9..89b36d277e 100644
--- a/pkg/wshutil/wshevent.go
+++ b/pkg/wshutil/wshevent.go
@@ -7,7 +7,7 @@ import (
"sync"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/wps"
+ "github.com/woveterm/wove/pkg/wps"
)
// event inverter. converts WaveEvents to a listener.On() API
diff --git a/pkg/wshutil/wshproxy.go b/pkg/wshutil/wshproxy.go
index 7e774686b7..98416058a0 100644
--- a/pkg/wshutil/wshproxy.go
+++ b/pkg/wshutil/wshproxy.go
@@ -7,9 +7,9 @@ import (
"fmt"
"sync"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type WshRpcProxy struct {
diff --git a/pkg/wshutil/wshrouter.go b/pkg/wshutil/wshrouter.go
index 375772b924..945f6ed521 100644
--- a/pkg/wshutil/wshrouter.go
+++ b/pkg/wshutil/wshrouter.go
@@ -15,10 +15,10 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const (
diff --git a/pkg/wshutil/wshrouter_controlimpl.go b/pkg/wshutil/wshrouter_controlimpl.go
index 970f9aaafd..88491614b7 100644
--- a/pkg/wshutil/wshrouter_controlimpl.go
+++ b/pkg/wshutil/wshrouter_controlimpl.go
@@ -8,12 +8,12 @@ import (
"fmt"
"log"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wstore"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wstore"
)
type WshRouterControlImpl struct {
diff --git a/pkg/wshutil/wshrpc.go b/pkg/wshutil/wshrpc.go
index 8a29e204bd..e5725af83f 100644
--- a/pkg/wshutil/wshrpc.go
+++ b/pkg/wshutil/wshrpc.go
@@ -16,13 +16,13 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/streamclient"
- "github.com/wavetermdev/waveterm/pkg/util/ds"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/streamclient"
+ "github.com/woveterm/wove/pkg/util/ds"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
const DefaultTimeoutMs = 5000
diff --git a/pkg/wshutil/wshrpcio.go b/pkg/wshutil/wshrpcio.go
index 2a88e987b4..9c3a39a95f 100644
--- a/pkg/wshutil/wshrpcio.go
+++ b/pkg/wshutil/wshrpcio.go
@@ -7,8 +7,8 @@ import (
"fmt"
"io"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/util/utilfn"
)
// special I/O wrappers for wshrpc
diff --git a/pkg/wshutil/wshstreamadapter.go b/pkg/wshutil/wshstreamadapter.go
index b83d1c727c..76390b2555 100644
--- a/pkg/wshutil/wshstreamadapter.go
+++ b/pkg/wshutil/wshstreamadapter.go
@@ -4,7 +4,7 @@
package wshutil
import (
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
type WshRpcStreamClientAdapter struct {
diff --git a/pkg/wshutil/wshutil.go b/pkg/wshutil/wshutil.go
index 2bb7e2db11..e8a79cc03e 100644
--- a/pkg/wshutil/wshutil.go
+++ b/pkg/wshutil/wshutil.go
@@ -18,14 +18,14 @@ import (
"sync/atomic"
"github.com/golang-jwt/jwt/v5"
- "github.com/wavetermdev/waveterm/pkg/baseds"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/util/packetparser"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wavejwt"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/baseds"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/util/packetparser"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wavejwt"
+ "github.com/woveterm/wove/pkg/wshrpc"
)
// these should both be 5 characters
diff --git a/pkg/wslconn/wsl-util.go b/pkg/wslconn/wsl-util.go
index 4e8876f402..05e20b392d 100644
--- a/pkg/wslconn/wsl-util.go
+++ b/pkg/wslconn/wsl-util.go
@@ -16,12 +16,12 @@ import (
"text/template"
"time"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/genconn"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/wsl"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/genconn"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/wsl"
)
func hasBashInstalled(ctx context.Context, client *wsl.Distro) (bool, error) {
diff --git a/pkg/wslconn/wslconn.go b/pkg/wslconn/wslconn.go
index 00eb584545..6ef1c6f2e9 100644
--- a/pkg/wslconn/wslconn.go
+++ b/pkg/wslconn/wslconn.go
@@ -15,22 +15,22 @@ import (
"sync/atomic"
"time"
- "github.com/wavetermdev/waveterm/pkg/blocklogger"
- "github.com/wavetermdev/waveterm/pkg/genconn"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
- "github.com/wavetermdev/waveterm/pkg/userinput"
- "github.com/wavetermdev/waveterm/pkg/util/shellutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wconfig"
- "github.com/wavetermdev/waveterm/pkg/wps"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
- "github.com/wavetermdev/waveterm/pkg/wsl"
+ "github.com/woveterm/wove/pkg/blocklogger"
+ "github.com/woveterm/wove/pkg/genconn"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/remote/conncontroller"
+ "github.com/woveterm/wove/pkg/telemetry"
+ "github.com/woveterm/wove/pkg/telemetry/telemetrydata"
+ "github.com/woveterm/wove/pkg/userinput"
+ "github.com/woveterm/wove/pkg/util/shellutil"
+ "github.com/woveterm/wove/pkg/util/utilfn"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wconfig"
+ "github.com/woveterm/wove/pkg/wps"
+ "github.com/woveterm/wove/pkg/wshrpc"
+ "github.com/woveterm/wove/pkg/wshutil"
+ "github.com/woveterm/wove/pkg/wsl"
)
const (
diff --git a/pkg/wstore/wstore.go b/pkg/wstore/wstore.go
index 76d271a71e..7a67cfe7a0 100644
--- a/pkg/wstore/wstore.go
+++ b/pkg/wstore/wstore.go
@@ -8,8 +8,8 @@ import (
"fmt"
"sync"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
)
func init() {
diff --git a/pkg/wstore/wstore_dboldmigration.go b/pkg/wstore/wstore_dboldmigration.go
index 17e09c8902..17dead5809 100644
--- a/pkg/wstore/wstore_dboldmigration.go
+++ b/pkg/wstore/wstore_dboldmigration.go
@@ -10,8 +10,8 @@ import (
"time"
"github.com/jmoiron/sqlx"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
)
const OldDBName = "~/.waveterm/waveterm.db"
diff --git a/pkg/wstore/wstore_dbops.go b/pkg/wstore/wstore_dbops.go
index e9a0289ee3..8f0e3862cf 100644
--- a/pkg/wstore/wstore_dbops.go
+++ b/pkg/wstore/wstore_dbops.go
@@ -11,10 +11,10 @@ import (
"regexp"
"time"
- "github.com/wavetermdev/waveterm/pkg/filestore"
- "github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/util/dbutil"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/filestore"
+ "github.com/woveterm/wove/pkg/panichandler"
+ "github.com/woveterm/wove/pkg/util/dbutil"
+ "github.com/woveterm/wove/pkg/waveobj"
)
var ErrNotFound = fmt.Errorf("not found")
diff --git a/pkg/wstore/wstore_dbsetup.go b/pkg/wstore/wstore_dbsetup.go
index 219fd31a1f..b367ad10d3 100644
--- a/pkg/wstore/wstore_dbsetup.go
+++ b/pkg/wstore/wstore_dbsetup.go
@@ -12,11 +12,11 @@ import (
"github.com/jmoiron/sqlx"
"github.com/sawka/txwrap"
- "github.com/wavetermdev/waveterm/pkg/util/migrateutil"
- "github.com/wavetermdev/waveterm/pkg/wavebase"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/util/migrateutil"
+ "github.com/woveterm/wove/pkg/wavebase"
+ "github.com/woveterm/wove/pkg/waveobj"
- dbfs "github.com/wavetermdev/waveterm/db"
+ dbfs "github.com/woveterm/wove/db"
)
const WStoreDBName = "waveterm.db"
diff --git a/pkg/wstore/wstore_rtinfo.go b/pkg/wstore/wstore_rtinfo.go
index 912a3ccac0..d1028c5d98 100644
--- a/pkg/wstore/wstore_rtinfo.go
+++ b/pkg/wstore/wstore_rtinfo.go
@@ -8,7 +8,7 @@ import (
"strings"
"sync"
- "github.com/wavetermdev/waveterm/pkg/waveobj"
+ "github.com/woveterm/wove/pkg/waveobj"
)
var (
diff --git a/tsunami/app/atom.go b/tsunami/app/atom.go
index 70f253d9bd..5732529298 100644
--- a/tsunami/app/atom.go
+++ b/tsunami/app/atom.go
@@ -8,8 +8,8 @@ import (
"reflect"
"runtime"
- "github.com/wavetermdev/waveterm/tsunami/engine"
- "github.com/wavetermdev/waveterm/tsunami/util"
+ "github.com/woveterm/wove/tsunami/engine"
+ "github.com/woveterm/wove/tsunami/util"
)
// AtomMeta provides metadata about an atom for validation and documentation
diff --git a/tsunami/app/defaultclient.go b/tsunami/app/defaultclient.go
index 1359f0f58b..21ec6652be 100644
--- a/tsunami/app/defaultclient.go
+++ b/tsunami/app/defaultclient.go
@@ -15,9 +15,9 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/tsunami/engine"
- "github.com/wavetermdev/waveterm/tsunami/util"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/engine"
+ "github.com/woveterm/wove/tsunami/util"
+ "github.com/woveterm/wove/tsunami/vdom"
)
const TsunamiCloseOnStdinEnvVar = "TSUNAMI_CLOSEONSTDIN"
diff --git a/tsunami/app/hooks.go b/tsunami/app/hooks.go
index 54418a00e0..9644fa9993 100644
--- a/tsunami/app/hooks.go
+++ b/tsunami/app/hooks.go
@@ -10,10 +10,10 @@ import (
"time"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/tsunami/engine"
- "github.com/wavetermdev/waveterm/tsunami/rpctypes"
- "github.com/wavetermdev/waveterm/tsunami/util"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/engine"
+ "github.com/woveterm/wove/tsunami/rpctypes"
+ "github.com/woveterm/wove/tsunami/util"
+ "github.com/woveterm/wove/tsunami/vdom"
)
// UseVDomRef provides a reference to a DOM element in the VDOM tree.
diff --git a/tsunami/build/build.go b/tsunami/build/build.go
index 54e4a901b9..94f1a5506b 100644
--- a/tsunami/build/build.go
+++ b/tsunami/build/build.go
@@ -24,12 +24,12 @@ import (
"syscall"
"time"
- "github.com/wavetermdev/waveterm/tsunami/util"
+ "github.com/woveterm/wove/tsunami/util"
"golang.org/x/mod/modfile"
)
const MinSupportedGoMinorVersion = 22
-const TsunamiUIImportPath = "github.com/wavetermdev/waveterm/tsunami/ui"
+const TsunamiUIImportPath = "github.com/woveterm/wove/tsunami/ui"
const MainAppFileName = "app.go"
type OutputCapture struct {
@@ -382,7 +382,7 @@ func createGoMod(tempDir, appNS, appName string, buildEnv *BuildEnv, opts BuildO
}
// Add requirement for tsunami SDK
- if err := modFile.AddRequire("github.com/wavetermdev/waveterm/tsunami", opts.SdkVersion); err != nil {
+ if err := modFile.AddRequire("github.com/woveterm/wove/tsunami", opts.SdkVersion); err != nil {
return fmt.Errorf("failed to add require directive: %w", err)
}
} else {
@@ -391,7 +391,7 @@ func createGoMod(tempDir, appNS, appName string, buildEnv *BuildEnv, opts BuildO
// Add replace directive for tsunami SDK if path is provided
if opts.SdkReplacePath != "" {
- if err := modFile.AddReplace("github.com/wavetermdev/waveterm/tsunami", "", opts.SdkReplacePath, ""); err != nil {
+ if err := modFile.AddReplace("github.com/woveterm/wove/tsunami", "", opts.SdkReplacePath, ""); err != nil {
return fmt.Errorf("failed to add replace directive: %w", err)
}
}
@@ -410,9 +410,9 @@ func createGoMod(tempDir, appNS, appName string, buildEnv *BuildEnv, opts BuildO
if verbose {
oc.Printf("[debug] Created go.mod with module path: %s", modulePath)
- oc.Printf("[debug] Added require: github.com/wavetermdev/waveterm/tsunami %s", opts.SdkVersion)
+ oc.Printf("[debug] Added require: github.com/woveterm/wove/tsunami %s", opts.SdkVersion)
if opts.SdkReplacePath != "" {
- oc.Printf("[debug] Added replace directive: github.com/wavetermdev/waveterm/tsunami => %s", opts.SdkReplacePath)
+ oc.Printf("[debug] Added replace directive: github.com/woveterm/wove/tsunami => %s", opts.SdkReplacePath)
}
}
diff --git a/tsunami/cmd/main-tsunami.go b/tsunami/cmd/main-tsunami.go
index f8b85f3e46..7bc25db8c4 100644
--- a/tsunami/cmd/main-tsunami.go
+++ b/tsunami/cmd/main-tsunami.go
@@ -6,8 +6,8 @@ import (
"path/filepath"
"github.com/spf13/cobra"
- "github.com/wavetermdev/waveterm/tsunami/build"
- "github.com/wavetermdev/waveterm/tsunami/tsunamibase"
+ "github.com/woveterm/wove/tsunami/build"
+ "github.com/woveterm/wove/tsunami/tsunamibase"
)
const (
diff --git a/tsunami/demo/cpuchart/app.go b/tsunami/demo/cpuchart/app.go
index a51e9217ea..2f916c6c53 100644
--- a/tsunami/demo/cpuchart/app.go
+++ b/tsunami/demo/cpuchart/app.go
@@ -5,8 +5,8 @@ import (
"time"
"github.com/shirou/gopsutil/v4/cpu"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/demo/githubaction/app.go b/tsunami/demo/githubaction/app.go
index 8839cadfea..0a8674d7cc 100644
--- a/tsunami/demo/githubaction/app.go
+++ b/tsunami/demo/githubaction/app.go
@@ -11,8 +11,8 @@ import (
"strconv"
"time"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/demo/modaltest/app.go b/tsunami/demo/modaltest/app.go
index ec9df7e64d..56fce1ea66 100644
--- a/tsunami/demo/modaltest/app.go
+++ b/tsunami/demo/modaltest/app.go
@@ -1,8 +1,8 @@
package main
import (
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/demo/pomodoro/app.go b/tsunami/demo/pomodoro/app.go
index 90c4e86728..e0155de0f8 100644
--- a/tsunami/demo/pomodoro/app.go
+++ b/tsunami/demo/pomodoro/app.go
@@ -4,8 +4,8 @@ import (
"fmt"
"time"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/demo/recharts/app.go b/tsunami/demo/recharts/app.go
index 72e8d31b5e..b364aaff52 100644
--- a/tsunami/demo/recharts/app.go
+++ b/tsunami/demo/recharts/app.go
@@ -4,8 +4,8 @@ import (
"math"
"time"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/demo/tabletest/app.go b/tsunami/demo/tabletest/app.go
index c028204e2c..cc9e535943 100644
--- a/tsunami/demo/tabletest/app.go
+++ b/tsunami/demo/tabletest/app.go
@@ -3,9 +3,9 @@ package main
import (
"fmt"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/ui"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/ui"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/demo/todo/app.go b/tsunami/demo/todo/app.go
index 7bb22692c4..4e1b6fc03f 100644
--- a/tsunami/demo/todo/app.go
+++ b/tsunami/demo/todo/app.go
@@ -4,8 +4,8 @@ import (
_ "embed"
"strconv"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/demo/tsunamiconfig/app.go b/tsunami/demo/tsunamiconfig/app.go
index eea8183b11..3f7940f85b 100644
--- a/tsunami/demo/tsunamiconfig/app.go
+++ b/tsunami/demo/tsunamiconfig/app.go
@@ -10,8 +10,8 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
var AppMeta = app.AppMeta{
diff --git a/tsunami/engine/clientimpl.go b/tsunami/engine/clientimpl.go
index ac9cb29109..5ddb6a438d 100644
--- a/tsunami/engine/clientimpl.go
+++ b/tsunami/engine/clientimpl.go
@@ -20,9 +20,9 @@ import (
"unicode"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/tsunami/rpctypes"
- "github.com/wavetermdev/waveterm/tsunami/util"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/rpctypes"
+ "github.com/woveterm/wove/tsunami/util"
+ "github.com/woveterm/wove/tsunami/vdom"
)
const TsunamiListenAddrEnvVar = "TSUNAMI_LISTENADDR"
diff --git a/tsunami/engine/comp.go b/tsunami/engine/comp.go
index 2bbea1a74a..a8c92dfb1b 100644
--- a/tsunami/engine/comp.go
+++ b/tsunami/engine/comp.go
@@ -3,7 +3,7 @@
package engine
-import "github.com/wavetermdev/waveterm/tsunami/vdom"
+import "github.com/woveterm/wove/tsunami/vdom"
// so components either render to another component (or fragment)
// or to a base element (text or vdom). base elements can then render children
diff --git a/tsunami/engine/errcomponent.go b/tsunami/engine/errcomponent.go
index a2a994e0fa..c0d7bbdb99 100644
--- a/tsunami/engine/errcomponent.go
+++ b/tsunami/engine/errcomponent.go
@@ -6,7 +6,7 @@ package engine
import (
"fmt"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/vdom"
)
// creates an error component for display when a component panics
diff --git a/tsunami/engine/globalctx.go b/tsunami/engine/globalctx.go
index 0d03a93710..e03afa42c0 100644
--- a/tsunami/engine/globalctx.go
+++ b/tsunami/engine/globalctx.go
@@ -7,7 +7,7 @@ import (
"sync"
"github.com/outrigdev/goid"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/vdom"
)
const (
diff --git a/tsunami/engine/hooks.go b/tsunami/engine/hooks.go
index 0da6eb6986..fdb6da7ba5 100644
--- a/tsunami/engine/hooks.go
+++ b/tsunami/engine/hooks.go
@@ -6,7 +6,7 @@ package engine
import (
"strconv"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/vdom"
)
// generic hook structure
diff --git a/tsunami/engine/render.go b/tsunami/engine/render.go
index 87750f8740..d6ee100779 100644
--- a/tsunami/engine/render.go
+++ b/tsunami/engine/render.go
@@ -10,9 +10,9 @@ import (
"unicode"
"github.com/google/uuid"
- "github.com/wavetermdev/waveterm/tsunami/rpctypes"
- "github.com/wavetermdev/waveterm/tsunami/util"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/rpctypes"
+ "github.com/woveterm/wove/tsunami/util"
+ "github.com/woveterm/wove/tsunami/vdom"
)
// see render.md for a complete guide to how tsunami rendering, lifecycle, and reconciliation works
diff --git a/tsunami/engine/rootelem.go b/tsunami/engine/rootelem.go
index 787be044e0..a8186a0a94 100644
--- a/tsunami/engine/rootelem.go
+++ b/tsunami/engine/rootelem.go
@@ -11,9 +11,9 @@ import (
"strings"
"sync"
- "github.com/wavetermdev/waveterm/tsunami/rpctypes"
- "github.com/wavetermdev/waveterm/tsunami/util"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/rpctypes"
+ "github.com/woveterm/wove/tsunami/util"
+ "github.com/woveterm/wove/tsunami/vdom"
)
const ChildrenPropKey = "children"
diff --git a/tsunami/engine/schema.go b/tsunami/engine/schema.go
index c51725c535..e392d43933 100644
--- a/tsunami/engine/schema.go
+++ b/tsunami/engine/schema.go
@@ -10,7 +10,7 @@ import (
"strings"
"time"
- "github.com/wavetermdev/waveterm/tsunami/util"
+ "github.com/woveterm/wove/tsunami/util"
)
// createStructDefinition creates a JSON schema definition for a struct type
diff --git a/tsunami/engine/serverhandlers.go b/tsunami/engine/serverhandlers.go
index 1e7bc94fda..7c0850fb77 100644
--- a/tsunami/engine/serverhandlers.go
+++ b/tsunami/engine/serverhandlers.go
@@ -16,9 +16,9 @@ import (
"sync"
"time"
- "github.com/wavetermdev/waveterm/tsunami/rpctypes"
- "github.com/wavetermdev/waveterm/tsunami/util"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/rpctypes"
+ "github.com/woveterm/wove/tsunami/util"
+ "github.com/woveterm/wove/tsunami/vdom"
)
const SSEKeepAliveDuration = 5 * time.Second
diff --git a/tsunami/rpctypes/protocoltypes.go b/tsunami/rpctypes/protocoltypes.go
index bad88a8745..e371debc42 100644
--- a/tsunami/rpctypes/protocoltypes.go
+++ b/tsunami/rpctypes/protocoltypes.go
@@ -6,7 +6,7 @@ package rpctypes
import (
"fmt"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/vdom"
)
// rendered element (output from rendering pipeline)
diff --git a/tsunami/ui/table.go b/tsunami/ui/table.go
index d040f994e4..1ee687a178 100644
--- a/tsunami/ui/table.go
+++ b/tsunami/ui/table.go
@@ -6,8 +6,8 @@ import (
"sort"
"strconv"
- "github.com/wavetermdev/waveterm/tsunami/app"
- "github.com/wavetermdev/waveterm/tsunami/vdom"
+ "github.com/woveterm/wove/tsunami/app"
+ "github.com/woveterm/wove/tsunami/vdom"
)
// Core table types
diff --git a/tsunami/vdom/vdom_test.go b/tsunami/vdom/vdom_test.go
index 528717f677..bfbcdf5c71 100644
--- a/tsunami/vdom/vdom_test.go
+++ b/tsunami/vdom/vdom_test.go
@@ -6,7 +6,7 @@ import (
"reflect"
"testing"
- "github.com/wavetermdev/waveterm/tsunami/util"
+ "github.com/woveterm/wove/tsunami/util"
)
func TestH(t *testing.T) {