From 2d3ea852558dcf92ae29749f02e9694cb79a8251 Mon Sep 17 00:00:00 2001
From: thzxx
Date: Tue, 23 Jun 2026 22:46:00 +0800
Subject: [PATCH] =?UTF-8?q?v0.12.9:=20Completion=20Gate=20=E6=9E=B6?=
=?UTF-8?q?=E6=9E=84=E4=BF=AE=E5=A4=8D=20+=20=E5=B7=A5=E4=BD=9C=E7=A9=BA?=
=?UTF-8?q?=E9=97=B4=E6=96=87=E4=BB=B6=E5=A4=B9=E9=80=89=E6=8B=A9=20+=20?=
=?UTF-8?q?=E6=90=9C=E7=B4=A2/Plan=20=E5=85=A8=E9=93=BE=E8=B7=AF=E5=8A=A0?=
=?UTF-8?q?=E5=9B=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fix:
- Completion Gate read_file 误报消除 — 缩窄匹配为仅明确工具声明 + 写后读豁免
- Gate 阻断重试消息重写 — 待办中与 Gate 拦截匹配时明确要求调工具而非模糊修正
- FileWriteDedupHook (pre_tool) + PlanAutoTrackHook (post_tool) — Harness 插件化
- 工作空间浏览按钮修复 — openFile→openDirectory,原生文件夹选择对话框
- pre_tool Hook 执行结果可阻断 — HookResult.passed=false 时拦截工具执行
- Plan 步骤上限 6 + 动词过滤 + 阶段标题过滤 — 适配不同模型格式
- Plan Mode 任务描述固化到系统提示词 — 压缩后不丢失任务目标
- 全部完成信号去重 — system 消息不重复注入
- handleReflecting 空响应 pop() 精确定位最后 assistant 消息
Refactor:
- agent-engine.ts 移除全部硬编码模块变量和函数 — 迁移到 Hook 系统
- extractPlanSteps 增加 ACTION_VERBS 动词过滤
- Plan rejection 不再重置 loopCount
---
README.md | 6 +-
package-lock.json | 4 +-
package.json | 2 +-
src/main/ipc.ts | 8 ++
src/main/menu.ts | 2 +-
src/main/preload.ts | 3 +-
src/renderer/components/settings-modal.ts | 11 +--
src/renderer/index.html | 2 +-
src/renderer/services/agent-engine.ts | 113 +++++++++++-----------
src/renderer/services/completion-gate.ts | 10 +-
src/renderer/services/hooks.ts | 108 +++++++++++++++++++++
src/renderer/types.d.ts | 1 +
12 files changed, 194 insertions(+), 76 deletions(-)
diff --git a/README.md b/README.md
index 5b57c7a..f69c18a 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
@@ -256,7 +256,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
```
-产出:`release/Metona Ollama Setup v0.12.5.exe`
+产出:`release/Metona Ollama Setup v0.12.9.exe`
## 🛠️ 常用命令
@@ -507,7 +507,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
```
-Output: `release/Metona Ollama Setup v0.12.5.exe`
+Output: `release/Metona Ollama Setup v0.12.9.exe`
## 🛠️ Common Commands
diff --git a/package-lock.json b/package-lock.json
index 68094e7..a263547 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "metona-ollama-desktop",
- "version": "0.12.5",
+ "version": "0.12.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "metona-ollama-desktop",
- "version": "0.12.5",
+ "version": "0.12.9",
"license": "MIT",
"dependencies": {
"ffmpeg-static": "^5.2.0",
diff --git a/package.json b/package.json
index b7ece6d..d13d3fc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "metona-ollama-desktop",
- "version": "0.12.5",
+ "version": "0.12.9",
"description": "Metona Ollama - TypeScript + Electron \u684c\u9762 AI \u804a\u5929\u5ba2\u6237\u7aef",
"main": "dist/main/main.js",
"author": "thzxx",
diff --git a/src/main/ipc.ts b/src/main/ipc.ts
index 68cb9cd..dfabb34 100644
--- a/src/main/ipc.ts
+++ b/src/main/ipc.ts
@@ -130,6 +130,14 @@ export async function setupIPC(): Promise {
return result.filePath;
});
+ ipcMain.handle('dialog:openDirectory', async () => {
+ const result = await dialog.showOpenDialog(mainWindow!, {
+ properties: ['openDirectory']
+ });
+ if (result.canceled) return null;
+ return result.filePaths;
+ });
+
ipcMain.handle('fs:readFile', async (_, filePath: string) => {
try {
const check = checkPathAllowed(path.resolve(filePath), 'read');
diff --git a/src/main/menu.ts b/src/main/menu.ts
index 39a6c8c..b707874 100644
--- a/src/main/menu.ts
+++ b/src/main/menu.ts
@@ -101,7 +101,7 @@ export function createMenu(): void {
dialog.showMessageBox(mainWindow!, {
type: 'info',
title: '关于 Metona Ollama',
- message: 'Metona Ollama Desktop v0.12.5',
+ message: 'Metona Ollama Desktop v0.12.9',
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
icon: getIconPath()
});
diff --git a/src/main/preload.ts b/src/main/preload.ts
index 35bb05b..0211920 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -19,7 +19,8 @@ contextBridge.exposeInMainWorld('metonaDesktop', {
},
dialog: {
openFile: (options?: unknown) => ipcRenderer.invoke('dialog:openFile', options),
- saveFile: (options?: unknown) => ipcRenderer.invoke('dialog:saveFile', options)
+ saveFile: (options?: unknown) => ipcRenderer.invoke('dialog:saveFile', options),
+ openDirectory: () => ipcRenderer.invoke('dialog:openDirectory')
},
fs: {
readFile: (filePath: string) => ipcRenderer.invoke('fs:readFile', filePath),
diff --git a/src/renderer/components/settings-modal.ts b/src/renderer/components/settings-modal.ts
index 007e18c..ea241e8 100644
--- a/src/renderer/components/settings-modal.ts
+++ b/src/renderer/components/settings-modal.ts
@@ -267,12 +267,11 @@ export function initSettingsModal(): void {
btnBrowseWorkspace.addEventListener('click', async () => {
const bridge = window.metonaDesktop;
if (!bridge?.isDesktop) return;
- const paths = await bridge.dialog.openFile({
- filters: [{ name: '文件夹', extensions: ['*'] }]
- });
- // Electron openFile 对话框不能直接选文件夹,这里用 saveFile 代替
- // 或者让用户手动输入路径
- showToast('请在输入框中直接修改路径', 'info');
+ const paths = await bridge.dialog.openDirectory();
+ if (paths && paths.length > 0) {
+ inputWorkspaceDir.value = paths[0];
+ inputWorkspaceDir.dispatchEvent(new Event('blur'));
+ }
});
// 双击输入框解锁编辑
inputWorkspaceDir.addEventListener('dblclick', () => {
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 6002790..99e035f 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -28,7 +28,7 @@