@omnicrawl/cli-win32-x64 0.2.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/omnicrawl.exe +0 -0
- package/host/omnicrawl/config/templates/config.example.toml +88 -26
- package/host/omnicrawl/config/templates/decision_models.example.toml +62 -7
- package/host/omnicrawl/config/templates/embedding_models.example.toml +31 -0
- package/host/omnicrawl/config/templates/subagents.example.toml +37 -38
- package/host/omnicrawl/extensions/onejev_worker.py +293 -0
- package/host/omnicrawl/templates/speaking-style.md +13 -0
- package/host/omnicrawl/templates/subagents/computer-use.md +75 -0
- package/host/omnicrawl/templates/system_prompt.md +5 -1
- package/host/omnicrawl-api.exe +0 -0
- package/host/omnicrawl-decision.exe +0 -0
- package/host/omnicrawl-host.exe +0 -0
- package/host/omnicrawl-mcp-server.exe +0 -0
- package/host/omnicrawl-tui.exe +0 -0
- package/host/omnicrawl.exe +0 -0
- package/host/rust/assets/config-templates/config.example.toml +88 -26
- package/host/rust/assets/config-templates/decision_models.example.toml +62 -7
- package/host/rust/assets/config-templates/embedding_models.example.toml +31 -0
- package/host/rust/assets/config-templates/subagents.example.toml +37 -38
- package/host/rust/assets/extensions/onejev_worker.py +293 -0
- package/host/rust/assets/templates/speaking-style.md +13 -0
- package/host/rust/assets/templates/subagents/computer-use.md +75 -0
- package/host/rust/assets/templates/system_prompt.md +5 -1
- package/package.json +1 -1
- package/version.json +3 -3
- package/host/omnicrawl/templates/model_reply_compaction_system.md +0 -28
- package/host/rust/assets/templates/model_reply_compaction_system.md +0 -28
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""OneJev 本地决策 worker:在命名管道(Windows)/ Unix socket 上提供 System One 判定。
|
|
2
|
+
|
|
3
|
+
为什么不用 `qev serve`:官方那个服务经 uvicorn/FastAPI 暴露 HTTP,本机调用方因此要为
|
|
4
|
+
一次纯内存判定多付一层 TCP + ASGI 中转。这个 worker 直接持有 `qev` 的引擎对象,
|
|
5
|
+
请求从管道进来、答案从管道出去,中间只有一次 JSON 编解码。
|
|
6
|
+
|
|
7
|
+
协议(NDJSON,一行一个 JSON 对象):
|
|
8
|
+
|
|
9
|
+
请求 {"model": "...", "state": {...}, "questions": {...}}
|
|
10
|
+
成功 {"model": "...", "answers": {...}, "usage": {...}, "qev": {...}}
|
|
11
|
+
失败 {"error": "可读原因"}
|
|
12
|
+
|
|
13
|
+
响应形状故意与 HTTP 端点一致,Rust 侧读答案的代码因此不必区分两种传输。
|
|
14
|
+
|
|
15
|
+
服务是**本机共用**的常驻进程,由 OmniCrawl 宿主以脱离进程的方式拉起(见
|
|
16
|
+
`omnicrawl-onejev` 的 `server` 模块):加载权重、创建端点、打出就绪标记,之后一直服务;
|
|
17
|
+
停止只能显式做(宿主按 PID 回收整棵进程树)。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import logging
|
|
25
|
+
import os
|
|
26
|
+
import socket
|
|
27
|
+
import sys
|
|
28
|
+
import threading
|
|
29
|
+
import time
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
# 就绪标记:宿主按它认定「服务已经能接请求」(写进服务日志)。
|
|
33
|
+
READY_MARKER = "[onejev] 服务就绪"
|
|
34
|
+
# 单次连接的等待上限(秒):客户端总是连上就发,静默超时说明对端已经不在了。
|
|
35
|
+
CONNECTION_TIMEOUT_SECONDS = 60.0
|
|
36
|
+
# 管道缓冲区:一次判定请求的 state 上限是 64k 字符级别,留一倍余量。
|
|
37
|
+
PIPE_BUFFER_BYTES = 128 * 1024
|
|
38
|
+
|
|
39
|
+
log = logging.getLogger("onejev.worker")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Decider:
|
|
43
|
+
"""引擎 + 判定;串行化——一次前向独占前缀缓存,并发只会互相踩。"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, engine, model_name: str) -> None:
|
|
46
|
+
self.engine = engine
|
|
47
|
+
self.model_name = model_name
|
|
48
|
+
self._lock = threading.Lock()
|
|
49
|
+
|
|
50
|
+
def answer(self, body: dict) -> dict:
|
|
51
|
+
from qev.schema import SystemOneRequest
|
|
52
|
+
|
|
53
|
+
request = SystemOneRequest.model_validate(body)
|
|
54
|
+
with self._lock:
|
|
55
|
+
response, meta = self.engine.decide(request)
|
|
56
|
+
response.model = self.model_name
|
|
57
|
+
payload = response.model_dump()
|
|
58
|
+
payload["qev"] = meta
|
|
59
|
+
return payload
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _handle_connection(decider: Decider, read_line, write_line, close) -> None:
|
|
63
|
+
"""一次连接 = 一次判定:读一行请求,回一行响应(成功或错误),然后收线。"""
|
|
64
|
+
try:
|
|
65
|
+
line = read_line()
|
|
66
|
+
if not line:
|
|
67
|
+
return
|
|
68
|
+
payload = decider.answer(json.loads(line))
|
|
69
|
+
except Exception as error: # 请求不合法 / 引擎报错:回一行可读原因,不猜答案
|
|
70
|
+
log.warning("判定失败:%s", error)
|
|
71
|
+
message = str(error) or error.__class__.__name__
|
|
72
|
+
try:
|
|
73
|
+
write_line(json.dumps({"error": message}, ensure_ascii=False))
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
else:
|
|
77
|
+
write_line(json.dumps(payload, ensure_ascii=False))
|
|
78
|
+
finally:
|
|
79
|
+
close()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --------------------------------------------------------------------------
|
|
83
|
+
# Windows:命名管道
|
|
84
|
+
# --------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def windows_pipe_io(endpoint: str, decider: Decider) -> None:
|
|
88
|
+
import ctypes
|
|
89
|
+
import ctypes.wintypes as wintypes
|
|
90
|
+
import msvcrt
|
|
91
|
+
|
|
92
|
+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
93
|
+
|
|
94
|
+
pipe_access_duplex = 0x00000003
|
|
95
|
+
pipe_type_byte = 0x00000000
|
|
96
|
+
pipe_readmode_byte = 0x00000000
|
|
97
|
+
pipe_wait = 0x00000000
|
|
98
|
+
pipe_unlimited_instances = 255
|
|
99
|
+
invalid_handle = wintypes.HANDLE(-1).value
|
|
100
|
+
error_pipe_connected = 535
|
|
101
|
+
|
|
102
|
+
kernel32.CreateNamedPipeW.restype = wintypes.HANDLE
|
|
103
|
+
kernel32.CreateNamedPipeW.argtypes = [
|
|
104
|
+
wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD,
|
|
105
|
+
wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, ctypes.c_void_p,
|
|
106
|
+
]
|
|
107
|
+
kernel32.ConnectNamedPipe.restype = wintypes.BOOL
|
|
108
|
+
kernel32.ConnectNamedPipe.argtypes = [wintypes.HANDLE, ctypes.c_void_p]
|
|
109
|
+
kernel32.PeekNamedPipe.restype = wintypes.BOOL
|
|
110
|
+
kernel32.PeekNamedPipe.argtypes = [
|
|
111
|
+
wintypes.HANDLE, ctypes.c_void_p, wintypes.DWORD,
|
|
112
|
+
ctypes.POINTER(wintypes.DWORD), ctypes.POINTER(wintypes.DWORD), ctypes.POINTER(wintypes.DWORD),
|
|
113
|
+
]
|
|
114
|
+
kernel32.CloseHandle.restype = wintypes.BOOL
|
|
115
|
+
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
|
|
116
|
+
|
|
117
|
+
def create_instance() -> int:
|
|
118
|
+
return kernel32.CreateNamedPipeW(
|
|
119
|
+
endpoint,
|
|
120
|
+
pipe_access_duplex,
|
|
121
|
+
pipe_type_byte | pipe_readmode_byte | pipe_wait,
|
|
122
|
+
pipe_unlimited_instances,
|
|
123
|
+
PIPE_BUFFER_BYTES,
|
|
124
|
+
PIPE_BUFFER_BYTES,
|
|
125
|
+
0,
|
|
126
|
+
None,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def serve_instance(handle: int) -> None:
|
|
130
|
+
fd = msvcrt.open_osfhandle(handle, os.O_RDWR | os.O_BINARY)
|
|
131
|
+
stream = os.fdopen(fd, "r+b", buffering=0)
|
|
132
|
+
|
|
133
|
+
def read_line() -> str:
|
|
134
|
+
buffer = bytearray()
|
|
135
|
+
deadline = time.monotonic() + CONNECTION_TIMEOUT_SECONDS
|
|
136
|
+
while b"\n" not in buffer:
|
|
137
|
+
available = wintypes.DWORD(0)
|
|
138
|
+
kernel32.PeekNamedPipe(
|
|
139
|
+
wintypes.HANDLE(handle), None, 0, None, ctypes.byref(available), None
|
|
140
|
+
)
|
|
141
|
+
if available.value:
|
|
142
|
+
chunk = stream.read(available.value)
|
|
143
|
+
if not chunk:
|
|
144
|
+
break
|
|
145
|
+
buffer += chunk
|
|
146
|
+
continue
|
|
147
|
+
if time.monotonic() > deadline:
|
|
148
|
+
break
|
|
149
|
+
time.sleep(0.005)
|
|
150
|
+
return bytes(buffer).split(b"\n", 1)[0].decode("utf-8", "replace")
|
|
151
|
+
|
|
152
|
+
def write_line(text: str) -> None:
|
|
153
|
+
stream.write(text.encode("utf-8") + b"\n")
|
|
154
|
+
|
|
155
|
+
# 关闭 fd 即关闭 handle(`open_osfhandle` 把它交给了 fd)。
|
|
156
|
+
_handle_connection(decider, read_line, write_line, stream.close)
|
|
157
|
+
|
|
158
|
+
# 实例先建出来再报就绪:宿主看到标记时端点必须已经能被连上。
|
|
159
|
+
first = create_instance()
|
|
160
|
+
if first == invalid_handle:
|
|
161
|
+
raise SystemExit(f"创建命名管道失败:{ctypes.get_last_error()}({endpoint})")
|
|
162
|
+
print(f"{READY_MARKER} endpoint={endpoint}", flush=True)
|
|
163
|
+
|
|
164
|
+
pending = first
|
|
165
|
+
while True:
|
|
166
|
+
handle = pending
|
|
167
|
+
if not kernel32.ConnectNamedPipe(wintypes.HANDLE(handle), None):
|
|
168
|
+
error = ctypes.get_last_error()
|
|
169
|
+
# ERROR_PIPE_CONNECTED 说明客户端已经连上了这个实例,照旧交给服务线程。
|
|
170
|
+
if error != error_pipe_connected:
|
|
171
|
+
kernel32.CloseHandle(wintypes.HANDLE(handle))
|
|
172
|
+
pending = create_instance()
|
|
173
|
+
continue
|
|
174
|
+
# 一个实例只服务一个连接;下一个实例提前建好,客户端不会撞到「管道不存在」。
|
|
175
|
+
pending = create_instance()
|
|
176
|
+
threading.Thread(target=serve_instance, args=(handle,), daemon=True).start()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# --------------------------------------------------------------------------
|
|
180
|
+
# Unix:Unix domain socket(没有 TCP 端口,也没有 HTTP 中转)
|
|
181
|
+
# --------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def unix_socket_io(endpoint: str, decider: Decider) -> None:
|
|
185
|
+
path = Path(endpoint)
|
|
186
|
+
if path.exists():
|
|
187
|
+
path.unlink()
|
|
188
|
+
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
189
|
+
server.bind(endpoint)
|
|
190
|
+
server.listen(64)
|
|
191
|
+
os.chmod(endpoint, 0o600)
|
|
192
|
+
print(f"{READY_MARKER} endpoint={endpoint}", flush=True)
|
|
193
|
+
|
|
194
|
+
def serve_connection(connection: socket.socket) -> None:
|
|
195
|
+
stream = connection.makefile("rwb", buffering=0)
|
|
196
|
+
|
|
197
|
+
def read_line() -> str:
|
|
198
|
+
connection.settimeout(CONNECTION_TIMEOUT_SECONDS)
|
|
199
|
+
line = stream.readline()
|
|
200
|
+
return line.decode("utf-8", "replace").rstrip("\n") if line else ""
|
|
201
|
+
|
|
202
|
+
def write_line(text: str) -> None:
|
|
203
|
+
stream.write(text.encode("utf-8") + b"\n")
|
|
204
|
+
|
|
205
|
+
def close() -> None:
|
|
206
|
+
try:
|
|
207
|
+
stream.close()
|
|
208
|
+
finally:
|
|
209
|
+
connection.close()
|
|
210
|
+
|
|
211
|
+
_handle_connection(decider, read_line, write_line, close)
|
|
212
|
+
|
|
213
|
+
while True:
|
|
214
|
+
connection, _ = server.accept()
|
|
215
|
+
threading.Thread(target=serve_connection, args=(connection,), daemon=True).start()
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# --------------------------------------------------------------------------
|
|
219
|
+
# 引擎装配(与 `qev serve` 同口径,但不启 HTTP 服务)
|
|
220
|
+
# --------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def build_engine(args: argparse.Namespace):
|
|
224
|
+
from qev.calibrate import Calibration
|
|
225
|
+
from qev.mm_engine import local_model_dir
|
|
226
|
+
|
|
227
|
+
model_path = local_model_dir(args.model)
|
|
228
|
+
local = Path(model_path) / "calibration.json"
|
|
229
|
+
calib_path = args.calibration or (str(local) if local.exists() else None)
|
|
230
|
+
calibration = Calibration.load(calib_path) if calib_path else None
|
|
231
|
+
if calib_path:
|
|
232
|
+
log.info("calibration: %s", calib_path)
|
|
233
|
+
|
|
234
|
+
config = json.loads((Path(model_path) / "config.json").read_text())
|
|
235
|
+
if args.multimodal or config.get("vision_config") is not None:
|
|
236
|
+
from qev.mm_engine import MMDecisionEngine
|
|
237
|
+
|
|
238
|
+
return MMDecisionEngine(
|
|
239
|
+
model_path,
|
|
240
|
+
device=args.device,
|
|
241
|
+
dtype=args.dtype,
|
|
242
|
+
calibration=calibration,
|
|
243
|
+
fork_mode=args.fork_mode,
|
|
244
|
+
head_dtype=args.head_dtype,
|
|
245
|
+
max_branch_tokens=args.max_branch_tokens,
|
|
246
|
+
max_request_tokens=args.max_request_tokens,
|
|
247
|
+
gpu_preprocess=not args.no_gpu_preprocess,
|
|
248
|
+
cuda_graphs=not args.no_cuda_graphs,
|
|
249
|
+
)
|
|
250
|
+
from qev.engine import DecisionEngine
|
|
251
|
+
|
|
252
|
+
return DecisionEngine(
|
|
253
|
+
model_path,
|
|
254
|
+
device=args.device,
|
|
255
|
+
dtype=args.dtype,
|
|
256
|
+
calibration=calibration,
|
|
257
|
+
fork_mode=args.fork_mode,
|
|
258
|
+
head_dtype=args.head_dtype,
|
|
259
|
+
max_branch_tokens=args.max_branch_tokens,
|
|
260
|
+
max_request_tokens=args.max_request_tokens,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def main() -> None:
|
|
265
|
+
parser = argparse.ArgumentParser(prog="onejev_worker")
|
|
266
|
+
parser.add_argument("--model", required=True, help="本地权重目录(或 Hub id)")
|
|
267
|
+
parser.add_argument("--name", default=None, help="对外的模型名")
|
|
268
|
+
parser.add_argument("--endpoint", required=True, help="命名管道名或 Unix socket 路径")
|
|
269
|
+
parser.add_argument("--device", default="cuda")
|
|
270
|
+
parser.add_argument("--dtype", default="bfloat16")
|
|
271
|
+
parser.add_argument("--head-dtype", default="float32")
|
|
272
|
+
parser.add_argument("--fork-mode", default="auto")
|
|
273
|
+
parser.add_argument("--multimodal", action="store_true")
|
|
274
|
+
parser.add_argument("--no-cuda-graphs", action="store_true")
|
|
275
|
+
parser.add_argument("--no-gpu-preprocess", action="store_true")
|
|
276
|
+
parser.add_argument("--calibration", default=None)
|
|
277
|
+
parser.add_argument("--max-branch-tokens", type=int, default=32768)
|
|
278
|
+
parser.add_argument("--max-request-tokens", type=int, default=65536)
|
|
279
|
+
args = parser.parse_args()
|
|
280
|
+
|
|
281
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
|
|
282
|
+
|
|
283
|
+
engine = build_engine(args)
|
|
284
|
+
decider = Decider(engine, args.name or Path(args.model).name)
|
|
285
|
+
|
|
286
|
+
if sys.platform == "win32":
|
|
287
|
+
windows_pipe_io(args.endpoint, decider)
|
|
288
|
+
else:
|
|
289
|
+
unix_socket_io(args.endpoint, decider)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
main()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# OmniCrawl 简洁回复模式
|
|
2
|
+
|
|
3
|
+
你当前处于 **Speaking Style(简洁回复模式)**。目标是在不丢结论的前提下把回复压到最短。
|
|
4
|
+
|
|
5
|
+
## 规则
|
|
6
|
+
|
|
7
|
+
- 面向用户的回复最多十行(列表项、空行都计入),优先控制在三到五行。
|
|
8
|
+
- 先给结论,再给必要依据;不复述需求,不写铺垫,不写总结段。
|
|
9
|
+
- 默认不用标题、表格与代码块;必须给命令或代码时只保留关键行。
|
|
10
|
+
- 只保留会改变用户决策的信息;过程细节、工具输出原文、探索记录一律不展开。
|
|
11
|
+
- 需要用户决策时,问题占一行,选项各占一行。
|
|
12
|
+
- 十行仍不足以表达时,保留结论与下一步,其余内容写进文件并只给路径。
|
|
13
|
+
- 简洁只压缩表达,不省略风险、失败、未验证项等关键限定;不得为了简短而编造结论。
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: computer-use
|
|
3
|
+
description: 在桌面窗口上执行点击、输入与拖拽,按截图判断下一步并回报阻塞点
|
|
4
|
+
tools:
|
|
5
|
+
- windows_window
|
|
6
|
+
- windows_screenshot
|
|
7
|
+
- windows_input
|
|
8
|
+
- windows_control
|
|
9
|
+
- read_image
|
|
10
|
+
- list
|
|
11
|
+
- read
|
|
12
|
+
model: inherit
|
|
13
|
+
permissionMode: standard
|
|
14
|
+
background: false
|
|
15
|
+
isolation: shared
|
|
16
|
+
skills: []
|
|
17
|
+
mcpServers: []
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
你是 OmniCrawl 的桌面操作子 Agent。你通过截图观察屏幕,用鼠标与键盘完成父 Agent 交代的目标。
|
|
21
|
+
|
|
22
|
+
## 一、坐标规则
|
|
23
|
+
|
|
24
|
+
- 截图工具返回 `source_bounds`(截取区域)与 `image.width/height`(实际图片尺寸)。
|
|
25
|
+
- `windows_input` 接受的是**桌面坐标**,即 `source_bounds` 坐标系里的像素。
|
|
26
|
+
- 图片未缩放(`scaled: false`):`x = source_bounds.left + 截图像素 x`,`y = source_bounds.top + 截图像素 y`。
|
|
27
|
+
- 图片被缩放(`scaled: true`):先按比例还原——
|
|
28
|
+
`x = source_bounds.left + 截图像素 x * source_bounds.width / image.width`,
|
|
29
|
+
`y = source_bounds.top + 截图像素 y * source_bounds.height / image.height`。
|
|
30
|
+
- 一律使用工具返回的真实 `image.width/height` 计算,不要用你请求的 `max_dimension` 反推。
|
|
31
|
+
- 本机进程是 DPI-unaware,截图与鼠标输入共用同一套虚拟化坐标,**不要**再乘任何缩放或 DPI 系数。
|
|
32
|
+
|
|
33
|
+
## 二、动作选择
|
|
34
|
+
|
|
35
|
+
- 能用 `windows_control` 语义定位的控件(按钮、输入框、菜单项),优先用它,不要猜坐标。
|
|
36
|
+
- 只有语义定位失效时才退回坐标点击。
|
|
37
|
+
- 点击后不要立刻截图:先做廉价校验(窗口标题变化、控件文本、剪贴板)。
|
|
38
|
+
- 连着几步操作(点地址栏 → 输入网址 → 回车)用一次 `actions` 传动作数组,按顺序执行。
|
|
39
|
+
- 浏览器前进后退用 `action=click` 配 `button=back` 或 `button=forward`,不要去点工具栏图标。
|
|
40
|
+
|
|
41
|
+
## 三、截图纪律
|
|
42
|
+
|
|
43
|
+
- `windows_input`、`windows_control`(除 `list`)、`windows_window` 的 `activate` 都会随结果带回一张操作后的画面(`screenshot` 字段 + 图片),核对效果直接看它,不要再调 `windows_screenshot`。
|
|
44
|
+
- 只有在这张自带截图缺失(结果里只有 `screenshot_error`)或需要看别处画面时才截图,且优先截目标窗口,不要截整屏。
|
|
45
|
+
- 必须截桌面时用 `target=desktop` 配上 `x`/`y`/`width`/`height` 裁剪到目标区域,别把整屏塞进上下文。
|
|
46
|
+
- 不要对同一画面重复截图;确认画面确有变化后再截。
|
|
47
|
+
- 同一坐标连续两次点击后画面仍无变化,判定为阻塞点,立即停下回报。
|
|
48
|
+
|
|
49
|
+
## 四、长按与拖拽
|
|
50
|
+
|
|
51
|
+
- 长按:`windows_input` 的 `action=click` 带 `hold_ms`。
|
|
52
|
+
- 拖拽:`action=press` → `action=move` → `action=release`,中间必须有移动事件。
|
|
53
|
+
- 任何 `press` 都必须配对 `release`;提前结束也要先 `release`。
|
|
54
|
+
|
|
55
|
+
## 五、边界
|
|
56
|
+
|
|
57
|
+
- 没有任何人工审核环节,破坏性操作(删除、卸载、关机、提交订单、发送消息)必须先停下,
|
|
58
|
+
把待确认的动作写进回报,由父 Agent 决定。
|
|
59
|
+
- 不要操作与目标无关的窗口。
|
|
60
|
+
- 不要输入疑似凭据的内容,也不要从屏幕上抄录密码、令牌。
|
|
61
|
+
|
|
62
|
+
## 六、回报格式
|
|
63
|
+
|
|
64
|
+
任务结束(完成、受阻或超时)时,用一段 JSON 收尾,不要输出其他内容:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"status": "completed | blocked",
|
|
69
|
+
"summary": "一句话说明结果",
|
|
70
|
+
"obstacle": "status=blocked 时写阻塞原因,否则为空串",
|
|
71
|
+
"screenshots": ["最后一张截图路径"]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`status=blocked` 表示需要父 Agent 决策或补充信息;不要在无计可施时反复重试同一动作。
|
|
@@ -24,6 +24,10 @@ OmniCrawl 是一个能够长期处理本地项目任务的 Agent。
|
|
|
24
24
|
- 写入前先搜索;填写 frontmatter(title、created、updated、project、tags、type note|meeting|decision|log|research|reference、status draft|done|archived)。文件应存放在 `projects/<project>/`、`topics/<topic>/` 或 `daily/YYYY/MM/` 下;绝不手动编辑 `INDEX.md`。
|
|
25
25
|
- 不得写入凭据或个人敏感信息;笔记必须是 UTF-8 Markdown。不要使用知识库存储会话进度、项目技术事实或稳定用户偏好;不要将知识库与 `omnicrawl://docs/` 或工作区文件混淆。
|
|
26
26
|
|
|
27
|
+
Skill 使用协议:
|
|
28
|
+
- Skill 目录不注入上下文:需要时先用 `skill_search` 检索索引(`query` 写任务关键词,`scope` 取 all/project/user/enterprise/path 收窄范围),命中后用 `read` 打开结果里的 `location` 读正文,再按其中的流程执行。
|
|
29
|
+
- Skill 目录常在 `~/.omnicrawl/skills/` 或 `.omnicrawl/skills/`(工作区之外),不要用 `find`、`grep` 去找 Skill。用户用 `/skill:<名>` 指定时,该 Skill 正文已在上下文里。
|
|
30
|
+
|
|
27
31
|
Todo 规划协议:
|
|
28
32
|
- 对于任何多步骤项目任务,先调用 `update_todos`,提交简洁、有序且具体的步骤列表,然后再检查或编辑文件。每完成一个重要步骤,就重新提交列表并将对应步骤的 `completed` 标记为 true;提交空列表表示清除计划。不要只用文字计划替代该工具调用。
|
|
29
33
|
|
|
@@ -57,7 +61,7 @@ omnicrawl文档(共 14 篇,均以 `omnicrawl://docs/<文件名>` 读取,
|
|
|
57
61
|
- 顾问策略(零参数 `advisor` 工具、`[advisor]` 配置、`/advisor` 命令、设置面板「顾问设置」):读取 `omnicrawl://docs/advisor_design.md`;实现位于 `rust/crates/omnicrawl-controllers/src/advisor.rs` 和 `rust/crates/omnicrawl-config/src/features/advisor.rs`。
|
|
58
62
|
- 工具输出压缩(外接小模型压缩工具结果、`[tool_output_compression]` 配置、设置面板「工具输出压缩」):读取 `omnicrawl://docs/tool_output_compression_design.md`;实现位于 `rust/crates/omnicrawl-controllers/src/compression.rs`。
|
|
59
63
|
- AI 消息脱敏(可逆占位符、匹配引擎、序号注册表、流式还原、`[desensitization]` 配置、设置面板「消息脱敏」):读取 `omnicrawl://docs/agent_gateway_desensitization_design.md`;实现位于 `rust/crates/omnicrawl-llm/src/desensitization/` 和 `rust/crates/omnicrawl-config/src/features/desensitization.rs`。
|
|
60
|
-
- TTS 语音合成(
|
|
64
|
+
- TTS 语音合成(Kokoro-82M ONNX CPU:配置、tts_synthesize 工具、CLI、音素化、模型下载):读取 `omnicrawl://docs/TTS.md`;实现位于 `rust/crates/omnicrawl-tts/` 和 `rust/crates/omnicrawl-config/src/features/tts.rs`。
|
|
61
65
|
- 决策接口(结构化决策模型的本地 REST 服务:启动、无鉴权的回环访问、decide/choice/rank/review 端点、常驻与复用、脱敏与安全边界):读取 `omnicrawl://docs/decision_api.md`;实现位于 `rust/crates/omnicrawl-decision/` 和 `rust/crates/omnicrawl-config/src/features/decision_model.rs`。
|
|
62
66
|
- FSAPP 接入(文件系统应用侧的接入约定):读取 `omnicrawl://docs/FSAPP.md`。
|
|
63
67
|
|
package/host/omnicrawl-api.exe
CHANGED
|
Binary file
|
|
Binary file
|
package/host/omnicrawl-host.exe
CHANGED
|
Binary file
|
|
Binary file
|
package/host/omnicrawl-tui.exe
CHANGED
|
Binary file
|
package/host/omnicrawl.exe
CHANGED
|
Binary file
|
|
@@ -49,6 +49,28 @@ default_context_window_tokens = 1000000
|
|
|
49
49
|
[llm.profiles.gemini-main.discovery]
|
|
50
50
|
enabled = true
|
|
51
51
|
|
|
52
|
+
# 联网搜索(web_search 工具):默认引擎在 TUI /settings → 工具设置 → 联网搜索 中切换,
|
|
53
|
+
# 也可在此手改。duckduckgo / arxiv / huggingface
|
|
54
|
+
# 抓各自站点的公开搜索页,无需凭据,统一走浏览器指纹环境;
|
|
55
|
+
# github 配了 github_token 时走 REST 检索接口,未配时仍抓公开搜索页;
|
|
56
|
+
# tavily / exa / firecrawl / tinyfish 走各自的检索接口,需要配置对应的 API Key(写入本地 config.toml,不提交仓库);
|
|
57
|
+
# anysearch 走统一检索接口,anysearch_api_key 可选——留空即按 IP 走匿名免费额度;
|
|
58
|
+
# tinyfish 走 GET 检索接口(每天 12,000 次以内免费),tinyfish_api_key 必填;
|
|
59
|
+
# 走哪个引擎只看这里的设置:工具调用里的 engine 参数无效,agent 不能按单次任务改搜索通路。
|
|
60
|
+
# mixed 是综合搜索:并发查询上述渠道并去重合并,接口型渠道只在配了凭据时参与(anysearch 始终参与)。
|
|
61
|
+
[web_search]
|
|
62
|
+
engine = "duckduckgo"
|
|
63
|
+
tavily_api_key = ""
|
|
64
|
+
exa_api_key = ""
|
|
65
|
+
firecrawl_api_key = ""
|
|
66
|
+
# AnySearch API Key(可选):留空走匿名额度,填了则按该 Key 的付费额度计费。
|
|
67
|
+
anysearch_api_key = ""
|
|
68
|
+
# TinyFish API Key(必填才可用):留空时调用该引擎会如实报缺凭据。
|
|
69
|
+
tinyfish_api_key = ""
|
|
70
|
+
# GitHub Token 可选:配置后 GitHub 引擎改走 REST 检索接口(30 次/分钟),
|
|
71
|
+
# 留空则抓公开搜索页(10 次/分钟)。token 只影响额度,无需勾选任何仓库权限。
|
|
72
|
+
github_token = ""
|
|
73
|
+
|
|
52
74
|
# 视觉模型代理:模型引用复用 llm.profiles / models.toml,不在这里存 API Key。
|
|
53
75
|
# models 按顺序作为故障转移链;主模型不支持视觉且此项开启时,图片会先交给这些模型分析。
|
|
54
76
|
# 模型原生视觉在设置页「视觉」按当前模型/渠道开关(models.toml 或 [llm.profiles.<id>] 的 native_vision)。
|
|
@@ -56,49 +78,70 @@ enabled = true
|
|
|
56
78
|
enabled = false
|
|
57
79
|
models = []
|
|
58
80
|
|
|
59
|
-
#
|
|
60
|
-
# TUI /settings → 图像生成 中配置;
|
|
61
|
-
#
|
|
81
|
+
# 图像生成:两条通道按 provider 二选一——OpenAI 兼容 Image API 与 Gemini 的
|
|
82
|
+
# generateContent。接口地址、Key、模型与默认参数在 TUI /settings → 图像生成 中配置;
|
|
83
|
+
# api_key 写入本地 config.toml(不提交仓库)。size/quality 是 OpenAI 通道的口径,
|
|
84
|
+
# aspect_ratio/image_size 是 Gemini 通道的口径,切换服务商不会丢掉另一家的取值。
|
|
62
85
|
[image_gen]
|
|
63
86
|
enabled = false
|
|
87
|
+
# 服务商:openai / gemini。
|
|
88
|
+
provider = "openai"
|
|
89
|
+
# OpenAI 通道:https://api.openai.com/v1;Gemini 通道:
|
|
90
|
+
# https://generativelanguage.googleapis.com/v1beta。
|
|
64
91
|
base_url = "https://api.openai.com/v1"
|
|
65
92
|
api_key = ""
|
|
66
93
|
api_key_env = "OPENAI_API_KEY"
|
|
94
|
+
# OpenAI 通道默认 gpt-image-2;Gemini 通道默认 gemini-2.5-flash-image。
|
|
67
95
|
model = "gpt-image-2"
|
|
96
|
+
# 仅 OpenAI 通道使用:auto 或 宽x高。
|
|
68
97
|
size = "auto"
|
|
98
|
+
# 仅 OpenAI 通道使用:auto / low / medium / high。
|
|
69
99
|
quality = "auto"
|
|
100
|
+
# 仅 OpenAI 通道使用:png / jpeg / webp(Gemini 恒为 png)。
|
|
70
101
|
output_format = "png"
|
|
102
|
+
# 仅 Gemini 通道使用:画面比例,14 种之一(留空由模型取默认)。
|
|
103
|
+
aspect_ratio = ""
|
|
104
|
+
# 仅 Gemini 通道使用:分辨率档位 1K / 2K / 4K(留空由模型取默认)。
|
|
105
|
+
image_size = ""
|
|
71
106
|
n = 1
|
|
72
107
|
timeout_seconds = 120
|
|
73
108
|
|
|
74
|
-
# TTS
|
|
75
|
-
#
|
|
76
|
-
# 2) 本地推理(可选,需构建时带 omnicrawl-tts/onnx feature):MOSS-TTS-Nano ONNX,
|
|
77
|
-
# 首次启用时在 TUI /settings → TTS 中下载模型(约 763MB)并选择音色。
|
|
109
|
+
# TTS 语音合成(云服务):两条通道按 tts_cloud.provider 二选一,本机不需要模型文件
|
|
110
|
+
# 或 espeak-ng 一类原生依赖,只要 HTTP 与密钥。在 TUI /settings → TTS 中配置。
|
|
78
111
|
[tts]
|
|
79
112
|
enabled = false
|
|
80
|
-
model_dir = ""
|
|
81
|
-
voice = "Junhao"
|
|
82
113
|
auto_play = true
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
device = "auto"
|
|
86
|
-
# codec 流式解码(低首字节延迟;默认开启,显存占用低,避免全量解码 OOM)。
|
|
87
|
-
streaming = true
|
|
114
|
+
# 回合结束后朗读助手回复(直接在宿主里合成播放,不经 tts_synthesize 工具、也不走审批)。
|
|
115
|
+
speak_replies = false
|
|
88
116
|
output_dir = ".omnicrawl/.agent_tmp/tts"
|
|
89
117
|
|
|
90
|
-
#
|
|
118
|
+
# 服务商路由:openai / gemini。
|
|
119
|
+
[tts_cloud]
|
|
120
|
+
provider = "openai"
|
|
121
|
+
|
|
122
|
+
# OpenAI 通道(provider = "openai" 时生效):POST {base_url}/audio/speech。
|
|
123
|
+
# api_key 写入本地 config.toml(不提交仓库),也可在设置面板直接填写。
|
|
91
124
|
[tts_api]
|
|
92
|
-
enabled = true
|
|
93
125
|
base_url = "https://api.openai.com/v1"
|
|
94
126
|
api_key = ""
|
|
95
|
-
api_key_env = "OPENAI_API_KEY"
|
|
96
127
|
model = "gpt-4o-mini-tts"
|
|
128
|
+
# 音色:alloy/ash/ballad/coral/echo/fable/nova/onyx/sage/shimmer/verse/marin/cedar。
|
|
97
129
|
voice = "alloy"
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
#
|
|
101
|
-
speed = 1
|
|
130
|
+
# 语气指令;只对 gpt-4o-mini-tts 一类模型生效,留空则不下发。
|
|
131
|
+
instructions = ""
|
|
132
|
+
# 语速(0.25~4.0);1 时不下发该字段。
|
|
133
|
+
speed = 1
|
|
134
|
+
timeout_seconds = 120
|
|
135
|
+
|
|
136
|
+
# Gemini 通道(provider = "gemini" 时生效):POST {base_url}/models/{model}:generateContent。
|
|
137
|
+
[tts_gemini]
|
|
138
|
+
base_url = "https://generativelanguage.googleapis.com/v1beta"
|
|
139
|
+
api_key = ""
|
|
140
|
+
model = "gemini-2.5-flash-preview-tts"
|
|
141
|
+
# 预置音色名(如 Kore、Puck、Zephyr)。
|
|
142
|
+
voice = "Kore"
|
|
143
|
+
# 语气指令:Gemini 没有独立的指令字段,这段文本作为正文前缀下发。
|
|
144
|
+
instructions = ""
|
|
102
145
|
timeout_seconds = 120
|
|
103
146
|
|
|
104
147
|
[approval]
|
|
@@ -116,6 +159,11 @@ show_thinking = true
|
|
|
116
159
|
[memory]
|
|
117
160
|
enabled = true
|
|
118
161
|
|
|
162
|
+
# Skill 额外路径:目录或 .md 文件,相对路径按工作区解析。
|
|
163
|
+
# 与内置作用域一起参与发现,扫描顺序排在最后;同名 Skill 由先扫描到的作用域胜出。
|
|
164
|
+
# [skills]
|
|
165
|
+
# extra_paths = ["./my-skills", "~/.omnicrawl/shared-skills"]
|
|
166
|
+
|
|
119
167
|
# 内置工具开关:默认除 powershell 关闭外,其余全部启用。
|
|
120
168
|
# 只在需要禁用某个工具时取消注释并设为 false。
|
|
121
169
|
# [tools]
|
|
@@ -147,6 +195,21 @@ auto_retry_errors = ["SERVICE_UNAVAILABLE"]
|
|
|
147
195
|
enabled = true
|
|
148
196
|
max_auto_followups = 3
|
|
149
197
|
|
|
198
|
+
# 桌面操作(Computer Use):让模型通过截图观察窗口,并用鼠标键盘完成 GUI 任务。
|
|
199
|
+
# 由专职子代理执行,与 [subagents] 总开关互相独立;每次任务占用整个桌面,因此并发恒为 1。
|
|
200
|
+
# 本功能按时间收敛(不限制步数),无人工审核:破坏性动作由子代理先回报确认。
|
|
201
|
+
[computer_use]
|
|
202
|
+
enabled = false
|
|
203
|
+
# 专用子代理使用的模型;留空或 inherit 表示继承主模型。
|
|
204
|
+
model = ""
|
|
205
|
+
# 单次 computer_use 调用的时间预算(秒),超时会把已完成进度返回给模型。
|
|
206
|
+
timeout_seconds = 600
|
|
207
|
+
# 截图长边上限:同时决定坐标换算比例,越小越省 Token。
|
|
208
|
+
screenshot_max_dimension = 1024
|
|
209
|
+
# 专用子代理的思考强度:low / medium / high / xhigh / max。
|
|
210
|
+
# 桌面任务靠看画面、点控件收敛,深推理换不回转得动的操作,默认 low。
|
|
211
|
+
thinking_effort = "low"
|
|
212
|
+
|
|
150
213
|
[context_compaction]
|
|
151
214
|
# 上下文压缩是 Agent 的基础能力。触发阈值(Token)也可在运行设置面板按当前
|
|
152
215
|
# 模型上下文窗口的百分比设置(5% 步进):trigger_context_tokens = 上下文窗口 × 百分比。
|
|
@@ -243,16 +306,15 @@ enabled = false
|
|
|
243
306
|
# [subagents]
|
|
244
307
|
# enabled = false
|
|
245
308
|
# max_depth = 1
|
|
246
|
-
#
|
|
247
|
-
# max_tasks_per_batch = 4
|
|
309
|
+
# 保留以兼容既有配置;子代理已改为回合内同步串行执行,本项不再生效。
|
|
248
310
|
# default_timeout_seconds = 3600
|
|
249
311
|
# model_request_concurrency = 2
|
|
250
312
|
# allow_background = false
|
|
251
313
|
# allow_fork = false
|
|
252
|
-
# allow_worktree =
|
|
253
|
-
# allow_standard_agent =
|
|
314
|
+
# allow_worktree = true
|
|
315
|
+
# allow_standard_agent = true
|
|
254
316
|
# allow_shared_workspace_writes = false
|
|
255
|
-
# enable_verify_agent =
|
|
317
|
+
# enable_verify_agent = true
|
|
256
318
|
# verify_command_timeout_seconds = 120
|
|
257
319
|
# task_retention_minutes = 60
|
|
258
320
|
# result_summary_chars = 6000
|