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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions electron.vite.config.1755071894873.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// electron.vite.config.ts
import { resolve } from "path";
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
import vue from "@vitejs/plugin-vue";
import autoprefixer from "autoprefixer";
import tailwind from "tailwindcss";
import vueDevTools from "vite-plugin-vue-devtools";
import svgLoader from "vite-svg-loader";
import monacoEditorPlugin from "vite-plugin-monaco-editor-esm";
import path from "node:path";
var electron_vite_config_default = defineConfig({
main: {
plugins: [
externalizeDepsPlugin({
exclude: ["mermaid", "dompurify"]
})
],
resolve: {
alias: {
"@": resolve("src/main/"),
"@shared": resolve("src/shared")
}
},
build: {
rollupOptions: {
external: ["sharp", "@duckdb/node-api"],
output: {
inlineDynamicImports: true,
manualChunks: void 0
// Disable automatic chunk splitting
}
}
}
},
preload: {
plugins: [externalizeDepsPlugin()],
resolve: {
alias: {
"@shared": resolve("src/shared")
}
},
build: {
rollupOptions: {
input: {
index: resolve("src/preload/index.ts"),
floating: resolve("src/preload/floating-preload.ts")
}
}
}
},
renderer: {
optimizeDeps: {
include: [
"monaco-editor",
"axios"
]
},
resolve: {
alias: {
"@": resolve("src/renderer/src"),
"@shell": resolve("src/renderer/shell"),
"@shared": resolve("src/shared"),
vue: "vue/dist/vue.esm-bundler.js"
}
},
css: {
postcss: {
// @ts-ignore
plugins: [tailwind(), autoprefixer()]
}
},
server: {
host: "0.0.0.0"
// 防止代理干扰,导致vite-electron之间ws://localhost:5713和http://localhost:5713通信失败、页面组件无法加载
},
plugins: [
monacoEditorPlugin({
languageWorkers: ["editorWorkerService", "typescript", "css", "html", "json"],
customDistPath(_root, buildOutDir, _base) {
return path.resolve(buildOutDir, "monacoeditorwork");
}
}),
vue(),
svgLoader(),
vueDevTools({
// use export LAUNCH_EDITOR=cursor instead
// launchEditor: 'cursor'
})
],
build: {
minify: "esbuild",
rollupOptions: {
input: {
shell: resolve("src/renderer/shell/index.html"),
index: resolve("src/renderer/index.html"),
floating: resolve("src/renderer/floating/index.html")
}
}
}
}
});
export {
electron_vite_config_default as default
};
104 changes: 104 additions & 0 deletions electron.vite.config.1755572548960.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// electron.vite.config.ts
import { resolve } from "path";
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
import vue from "@vitejs/plugin-vue";
import autoprefixer from "autoprefixer";
import tailwind from "tailwindcss";
import vueDevTools from "vite-plugin-vue-devtools";
import svgLoader from "vite-svg-loader";
import monacoEditorPlugin from "vite-plugin-monaco-editor-esm";
import path from "node:path";
var electron_vite_config_default = defineConfig({
main: {
plugins: [
externalizeDepsPlugin({
exclude: ["mermaid", "dompurify"]
})
],
resolve: {
alias: {
"@": resolve("src/main/"),
"@shared": resolve("src/shared")
}
},
build: {
rollupOptions: {
external: ["sharp", "@duckdb/node-api"],
output: {
inlineDynamicImports: true,
manualChunks: void 0
// Disable automatic chunk splitting
}
}
}
},
preload: {
plugins: [externalizeDepsPlugin()],
resolve: {
alias: {
"@shared": resolve("src/shared")
}
},
build: {
rollupOptions: {
input: {
index: resolve("src/preload/index.ts"),
floating: resolve("src/preload/floating-preload.ts")
}
}
}
},
renderer: {
optimizeDeps: {
include: [
"monaco-editor",
"axios"
]
},
resolve: {
alias: {
"@": resolve("src/renderer/src"),
"@shell": resolve("src/renderer/shell"),
"@shared": resolve("src/shared"),
vue: "vue/dist/vue.esm-bundler.js"
}
},
css: {
postcss: {
// @ts-ignore
plugins: [tailwind(), autoprefixer()]
}
},
server: {
host: "0.0.0.0"
// 防止代理干扰,导致vite-electron之间ws://localhost:5713和http://localhost:5713通信失败、页面组件无法加载
},
plugins: [
monacoEditorPlugin({
languageWorkers: ["editorWorkerService", "typescript", "css", "html", "json"],
customDistPath(_root, buildOutDir, _base) {
return path.resolve(buildOutDir, "monacoeditorwork");
}
}),
vue(),
svgLoader(),
vueDevTools({
// use export LAUNCH_EDITOR=cursor instead
// launchEditor: 'cursor'
})
],
build: {
minify: "esbuild",
rollupOptions: {
input: {
shell: resolve("src/renderer/shell/index.html"),
index: resolve("src/renderer/index.html"),
floating: resolve("src/renderer/floating/index.html")
}
}
}
}
});
export {
electron_vite_config_default as default
};
Comment on lines +1 to +104
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Duplicate configuration file with identical content

This file appears to be an exact duplicate of electron.vite.config.1755071894873.mjs. Having multiple identical configuration files can lead to confusion and maintenance issues.

Consider removing the duplicate file and keeping only one configuration file to avoid confusion.


🏁 Script executed:

#!/bin/bash
# Check if both files are identical
if cmp -s "electron.vite.config.1755071894873.mjs" "electron.vite.config.1755572548960.mjs"; then
    echo "Files are identical"
else
    echo "Files differ"
    diff -u "electron.vite.config.1755071894873.mjs" "electron.vite.config.1755572548960.mjs" | head -20
fi

# Check if there's a primary config file
if [ -f "electron.vite.config.ts" ]; then
    echo "Found primary TypeScript config: electron.vite.config.ts"
fi

# Check references to these config files
echo -e "\nReferences to config files:"
rg -n "electron\.vite\.config\.(1755071894873|1755572548960|ts)" --type json --type js --type ts

Length of output: 644


🏁 Script executed:

#!/bin/bash
# Verify both config files exist
if [ ! -f electron.vite.config.1755071894873.mjs ] || [ ! -f electron.vite.config.1755572548960.mjs ]; then
  echo "ERROR: One or both config files not found."
  exit 1
fi

# Compare checksums to determine if files are truly identical
if command -v md5sum >/dev/null 2>&1; then
  echo "Using md5sum to compare files:"
  md5sum electron.vite.config.1755071894873.mjs electron.vite.config.1755572548960.mjs
elif command -v shasum >/dev/null 2>&1; then
  echo "Using shasum to compare files:"
  shasum -a 256 electron.vite.config.1755071894873.mjs electron.vite.config.1755572548960.mjs
else
  echo "No suitable hashing tool (md5sum/shasum) available."
  exit 1
fi

# Check for a single canonical TypeScript config
if [ -f electron.vite.config.ts ]; then
  echo "Found primary TypeScript config: electron.vite.config.ts"
else
  echo "No primary TypeScript config (electron.vite.config.ts) found."
fi

# Search codebase for any references to these timestamped config files
echo
echo "References to timestamped config files in repository:"
grep -R --include='*.mjs' --include='*.ts' "electron.vite.config.1755071894873" -n . || echo "  (none found)"
grep -R --include='*.mjs' --include='*.ts' "electron.vite.config.1755572548960" -n . || echo "  (none found)"

Length of output: 968


Remove duplicate timestamped Vite config files

Both electron.vite.config.1755071894873.mjs and electron.vite.config.1755572548960.mjs are bit-for-bit identical (same SHA-256 checksum) and aren’t referenced anywhere in the codebase. Keeping both can cause confusion and accidental drift.

• Delete one (or both) of the timestamped .mjs files – e.g.:

  • Remove electron.vite.config.1755071894873.mjs
  • Remove electron.vite.config.1755572548960.mjs

• Consolidate on the single canonical config:

  • Use electron.vite.config.ts as your primary Vite/Electron configuration.
  • Update any build or start scripts to reference the .ts file directly, if they currently point to a timestamped .mjs.

This cleanup will reduce maintenance overhead and eliminate ambiguity about which config is “authoritative.”

🤖 Prompt for AI Agents
In electron.vite.config.1755572548960.mjs lines 1-104: these timestamped .mjs
Vite config files are duplicates and unreferenced; delete one or both duplicate
files (e.g., remove electron.vite.config.1755071894873.mjs and
electron.vite.config.1755572548960.mjs), consolidate on the canonical
electron.vite.config.ts, and update any build/start scripts or config references
to point to electron.vite.config.ts instead of the timestamped .mjs filenames.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@google/genai": "^1.13.0",
"@jxa/run": "^1.4.0",
"@modelcontextprotocol/sdk": "^1.17.2",
"@nut-tree/nut-js": "^4.2.0",
"axios": "^1.7.9",
"better-sqlite3-multiple-ciphers": "11.10.0",
"cheerio": "^1.0.0",
Expand All @@ -80,6 +81,7 @@
"https-proxy-agent": "^7.0.6",
"jsonrepair": "^3.13.0",
"mammoth": "^1.9.0",
"marked": "^16.1.2",
"mime-types": "^2.1.35",
Comment on lines +84 to 85
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Sanitize Markdown rendering to prevent XSS in the renderer

Adding marked is fine, but never inject its output without sanitization. Ensure DOMPurify (or equivalent) is applied, and avoid v-html with untrusted content.

If you’re using marked directly, wrap its output like:

import DOMPurify from 'dompurify'
const html = DOMPurify.sanitize(marked(markdown))

Quick check for sanitization paths:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\bmarked\(|MarkdownRenderer|v-html' src/renderer | sed -n '1,120p'
rg -nP -C2 'DOMPurify|sanitize' src/renderer | sed -n '1,120p'

Length of output: 10324


🏁 Script executed:

#!/bin/bash
# Locate renderMarkdown imports and definitions
rg -nP 'import\s+.*renderMarkdown' -n src/renderer
rg -nP 'export\s+function\s+renderMarkdown' -C4 -n src/renderer
# Check if MarkdownRenderer.vue uses v-html without sanitization
rg -nP 'v-html' -C3 src/renderer/src/components/markdown/MarkdownRenderer.vue

Length of output: 743


Enforce sanitization on all v-html renderings

We’ve identified several places where untrusted HTML is rendered via v-html without any sanitization, which poses an XSS risk. Please apply DOMPurify.sanitize (or a comparable HTML sanitizer) to every such output:

• src/renderer/src/components/settings/AboutUsSettings.vue (line 126)

- <div class="… overflow-y-auto" v-html="disclaimerContent"></div>
+ <div class="… overflow-y-auto"
+      v-html="DOMPurify.sanitize(disclaimerContent)"></div>

Add at top:

import DOMPurify from 'dompurify'

• src/renderer/src/components/ui/UpdateDialog.vue (line 13)

- v-html="renderMarkdown(getCommonMarkdown(), upgrade.updateInfo?.releaseNotes || '')"
+ v-html="DOMPurify.sanitize(
+     renderMarkdown(getCommonMarkdown(), upgrade.updateInfo?.releaseNotes || '')
+   )"

Ensure import DOMPurify from 'dompurify' is present.

• src/renderer/src/components/message/MessageBlockThink.vue (line 24)

- <div … v-html="renderedContent"></div>
+ <div … v-html="DOMPurify.sanitize(renderedContent)"></div>

• src/renderer/src/components/artifacts/SvgArtifact.vue (line 2)

- const sanitizedContent = computed(() => props.block.content || '')
+ const sanitizedContent = computed(() =>
+   DOMPurify.sanitize(props.block.content || '', { SAFE_FOR_SVG: true })
+ )

• src/renderer/src/components/artifacts/HTMLArtifact.vue
Re-enable and adjust the commented‐out sanitizer code:

- // const sanitizedContent = computed(() => {
- //   if (!props.block.content) return ''
- //   return DOMPurify.sanitize(props.block.content, {
- //     WHOLE_DOCUMENT: true,
- //     ADD_TAGS: ['script', 'style'],
- //   })
- // })
+ const sanitizedContent = computed(() =>
+   DOMPurify.sanitize(props.block.content || '', {
+     WHOLE_DOCUMENT: true,
+     ADD_TAGS: ['script', 'style'],
+   })
+ )

Optional cleanup: remove the <!-- eslint-disable vue/no-v-html --> comments in MarkdownRenderer.vue, since it doesn’t actually use v-html and you should restrict disabling this rule only where truly necessary.

🤖 Prompt for AI Agents
In package.json lines 84-85 and across the renderer files:
src/renderer/src/components/settings/AboutUsSettings.vue (around line 126),
src/renderer/src/components/ui/UpdateDialog.vue (around line 13),
src/renderer/src/components/message/MessageBlockThink.vue (around line 24),
src/renderer/src/components/artifacts/SvgArtifact.vue (near top), and
src/renderer/src/components/artifacts/HTMLArtifact.vue, the review flags
unsanitized v-html usages; import DOMPurify from 'dompurify' at the top of each
listed component (or ensure the import already exists) and replace direct v-html
bindings with sanitized content by passing the HTML through
DOMPurify.sanitize(...) before rendering; in HTMLArtifact.vue re-enable and
adapt the previously commented sanitizer code to sanitize artifact HTML output;
optionally remove unnecessary <!-- eslint-disable vue/no-v-html --> in
MarkdownRenderer.vue and only keep the rule disabled where v-html is truly
required.

"nanoid": "^5.1.5",
"ollama": "^0.5.16",
Expand Down Expand Up @@ -152,10 +154,10 @@
"vite-plugin-monaco-editor-esm": "^2.0.2",
"vite-plugin-vue-devtools": "^8.0.0",
"vite-svg-loader": "^5.1.0",
"vue-renderer-markdown": "^0.0.34",
"vitest": "^3.2.4",
"vue": "^3.5.18",
"vue-i18n": "^11.1.11",
"vue-renderer-markdown": "^0.0.34",
"vue-router": "4",
"vue-tsc": "^2.2.10",
"vue-use-monaco": "^0.0.6",
Expand Down
2 changes: 1 addition & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ app.whenReady().then(async () => {
targetWindow.focus() // 确保窗口置顶

// 触发更新
presenter.upgradePresenter.checkUpdate()
// presenter.upgradePresenter.checkUpdate()
})

// 监听显示/隐藏窗口事件 (从托盘或快捷键或悬浮窗口触发)
Expand Down
6 changes: 3 additions & 3 deletions src/main/presenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { LLMProviderPresenter } from './llmProviderPresenter'
import { ConfigPresenter } from './configPresenter'
import { ThreadPresenter } from './threadPresenter'
import { DevicePresenter } from './devicePresenter'
import { UpgradePresenter } from './upgradePresenter'
// import { UpgradePresenter } from './upgradePresenter'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Remove updater references rather than commenting them out; verify no remaining usages

If the updater is intentionally disabled, delete the import, property, and instantiation to avoid drift and accidental reintroduction. Commented-out members on a central class invite merge conflicts later.

Apply removals in these spots:

-// import { UpgradePresenter } from './upgradePresenter'
-  // upgradePresenter: UpgradePresenter
-    // this.upgradePresenter = new UpgradePresenter()

And confirm there are no lingering references:

Also applies to: 48-48, 80-81


🏁 Script executed:

#!/bin/bash
rg -n -C2 '\bupgradePresenter\b|UpgradePresenter' --glob '!**/node_modules/**'

Length of output: 3848


Remove all commented-out UpgradePresenter references and related artifacts

The upgrade functionality appears to be fully disabled, yet there are numerous commented-out references, type declarations, store hooks, and documentation entries that should be cleaned up to prevent code drift and merge conflicts. Please apply the following removals and refactorings:

• In src/main/presenter/index.ts
– Remove the import on line 14
– Remove the commented-out property on line 48
– Remove the commented-out instantiation on line 80

• Delete the entire src/main/presenter/upgradePresenter/ folder (no active code is used)

• In src/shared/presenter.d.ts
– Remove the commented-out upgradePresenter: IUpgradePresenter declaration (around line 322)
– Remove the commented-out IUpgradePresenter interface (around lines 865–867)

• In src/renderer/src/stores/upgrade.ts
– Remove the commented-out usePresenter('upgradePresenter') hook on line 7

• In documentation files:
– Remove the UpgradePresenter entry in docs/event-system-design.md (line 58)
– Remove the UpgradePresenter box in docs/deepchat-architecture-overview.md (line 22)

After applying these changes, run:

rg -n '\bupgradePresenter\b|UpgradePresenter\b' --glob '!**/node_modules/**'

to confirm no residual references remain.

🤖 Prompt for AI Agents
In src/main/presenter/index.ts around lines 14, 48 and 80 remove the
commented-out import on line 14, the commented-out property on line 48, and the
commented-out instantiation on line 80; delete the entire
src/main/presenter/upgradePresenter/ folder; in src/shared/presenter.d.ts remove
the commented-out upgradePresenter: IUpgradePresenter declaration (around line
322) and the commented-out IUpgradePresenter interface (around lines 865–867);
in src/renderer/src/stores/upgrade.ts remove the commented-out
usePresenter('upgradePresenter') hook on line 7; in docs/event-system-design.md
remove the UpgradePresenter entry (line 58) and in
docs/deepchat-architecture-overview.md remove the UpgradePresenter box (line
22); after making these deletions run rg -n
'\bupgradePresenter\b|UpgradePresenter\b' --glob '!**/node_modules/**' to
confirm no residual references remain.

import { FilePresenter } from './filePresenter/FilePresenter'
import { McpPresenter } from './mcpPresenter'
import { SyncPresenter } from './syncPresenter'
Expand Down Expand Up @@ -45,7 +45,7 @@ export class Presenter implements IPresenter {
configPresenter: ConfigPresenter
threadPresenter: ThreadPresenter
devicePresenter: DevicePresenter
upgradePresenter: UpgradePresenter
// upgradePresenter: UpgradePresenter
shortcutPresenter: ShortcutPresenter
filePresenter: FilePresenter
mcpPresenter: McpPresenter
Expand Down Expand Up @@ -77,7 +77,7 @@ export class Presenter implements IPresenter {
this.configPresenter
)
this.mcpPresenter = new McpPresenter(this.configPresenter)
this.upgradePresenter = new UpgradePresenter()
// this.upgradePresenter = new UpgradePresenter()
this.shortcutPresenter = new ShortcutPresenter(this.configPresenter)
this.filePresenter = new FilePresenter()
this.syncPresenter = new SyncPresenter(this.configPresenter, this.sqlitePresenter)
Expand Down
61 changes: 61 additions & 0 deletions src/main/presenter/llmProviderPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,41 @@ export class LLMProviderPresenter implements ILlmProviderPresenter {

if (abortController.signal.aborted) break // Check after tool call returns

// 检查是否需要直接返回工具调用结果,不经过AI二次处理
const directReturnValue =
typeof toolResponse.rawData.directReturn === 'boolean'
? toolResponse.rawData.directReturn
: toolResponse.rawData.directReturn?.aiChange
console.log('index-toolResponse.rawData.directReturn', directReturnValue)
if (directReturnValue) {
console.log(
`[Agent Loop] Direct return tool result for ${toolCall.name}, skipping AI processing`
Comment on lines +825 to +832
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Direct-return detection reads rawData only; also check top-level 'directReturn'.

ToolManager now sets directReturn at top-level and (after the fix) inside rawData. Make detection robust by checking both.

Apply this diff:

-                const directReturnValue =
-                  typeof toolResponse.rawData.directReturn === 'boolean'
-                    ? toolResponse.rawData.directReturn
-                    : toolResponse.rawData.directReturn?.aiChange
+                const directReturnValue =
+                  (typeof (toolResponse as any).directReturn === 'boolean'
+                    ? (toolResponse as any).directReturn
+                    : undefined) ??
+                  (typeof toolResponse.rawData?.directReturn === 'boolean'
+                    ? toolResponse.rawData.directReturn
+                    : toolResponse.rawData?.directReturn?.aiChange)

Also applies to: 835-857

🤖 Prompt for AI Agents
In src/main/presenter/llmProviderPresenter/index.ts around lines 825-832 (and
also apply same change to 835-857), the direct-return detection only inspects
toolResponse.rawData.directReturn; update the logic to first check the top-level
toolResponse.directReturn (which may be boolean) and then fall back to
rawData.directReturn (handling both boolean and object with aiChange) so the
code respects directReturn set at either location; replace the current
conditional computation with one that checks toolResponse.directReturn ||
(typeof rawData.directReturn === 'boolean' ? rawData.directReturn :
rawData.directReturn?.aiChange) and use that combined value where
directReturnValue is used.

)

// 直接返回工具调用结果,不继续对话
yield {
type: 'response',
data: {
eventId,
tool_call: 'end',
tool_call_id: toolCall.id,
tool_call_name: toolCall.name,
tool_call_params: toolCall.arguments,
tool_call_server_name: toolDef.server.name,
tool_call_server_icons: toolDef.server.icons,
tool_call_server_description: toolDef.server.description,
tool_call_response: toolResponse.content,
tool_call_response_raw: toolResponse.rawData,
direct_return: true
}
}

// 结束agent循环
console.log(`[Agent Loop] Ending agent loop for direct return, event: ${eventId}`)
needContinueConversation = false
break
}

// Check if permission is required
if (toolResponse.rawData.requiresPermission) {
console.log(
Expand Down Expand Up @@ -919,6 +954,19 @@ export class LLMProviderPresenter implements ILlmProviderPresenter {
tool_call_response_raw: toolResponse.rawData // Full raw data
}
}

// 如果工具响应中包含 directReturn 标志,则直接返回结果,不继续对话
if (toolResponse.rawData && toolResponse.rawData.directReturn === true) {
console.log(
`[LLMProviderPresenter] Tool response has directReturn flag, ending agent loop`
)
// 发送结束事件
yield {
type: 'end',
data: { eventId, userStop: false }
}
return // 结束代理循环
}
} else {
// Non-native FC: Add tool execution record to conversation history for next LLM turn.

Expand Down Expand Up @@ -981,6 +1029,19 @@ export class LLMProviderPresenter implements ILlmProviderPresenter {
tool_call_response_raw: toolResponse.rawData // Full raw data for ThreadPresenter to store
}
}

// 如果工具响应中包含 directReturn 标志,则直接返回结果,不继续对话
if (toolResponse.rawData && toolResponse.rawData.directReturn === true) {
console.log(
`[LLMProviderPresenter] Tool response has directReturn flag, ending agent loop`
)
// 发送结束事件
yield {
type: 'end',
data: { eventId, userStop: false }
}
return // 结束代理循环
}
}
} catch (toolError) {
if (abortController.signal.aborted) break // Check after tool error
Expand Down
Loading