@omnicrawl/cli-win32-ia32 0.2.8 → 0.3.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 +66 -24
- package/host/omnicrawl/config/templates/decision_models.example.toml +50 -5
- package/host/omnicrawl/config/templates/subagents.example.toml +38 -38
- package/host/omnicrawl/extensions/onejev_worker.py +293 -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 +66 -24
- package/host/rust/assets/config-templates/decision_models.example.toml +50 -5
- package/host/rust/assets/config-templates/subagents.example.toml +38 -38
- package/host/rust/assets/extensions/onejev_worker.py +293 -0
- package/host/rust/assets/templates/system_prompt.md +5 -1
- package/package.json +1 -1
- package/version.json +3 -3
|
@@ -49,6 +49,22 @@ default_context_window_tokens = 1000000
|
|
|
49
49
|
[llm.profiles.gemini-main.discovery]
|
|
50
50
|
enabled = true
|
|
51
51
|
|
|
52
|
+
# 联网搜索(web_search 工具):默认引擎在 TUI /settings → 工具设置 → 联网搜索 中切换,
|
|
53
|
+
# 也可在此手改。bing / duckduckgo / arxiv / huggingface
|
|
54
|
+
# 抓各自站点的公开搜索页,无需凭据,统一走浏览器指纹环境;
|
|
55
|
+
# github 配了 github_token 时走 REST 检索接口,未配时仍抓公开搜索页;
|
|
56
|
+
# tavily / exa / firecrawl 走各自的检索接口,需要配置对应的 API Key(写入本地 config.toml,不提交仓库);
|
|
57
|
+
# mixed 是混合搜索:并发查询上述渠道并去重合并,接口型渠道只在配了凭据时参与。
|
|
58
|
+
# 工具调用时传 engine 参数可覆盖这里的默认引擎。
|
|
59
|
+
[web_search]
|
|
60
|
+
engine = "bing"
|
|
61
|
+
tavily_api_key = ""
|
|
62
|
+
exa_api_key = ""
|
|
63
|
+
firecrawl_api_key = ""
|
|
64
|
+
# GitHub Token 可选:配置后 GitHub 引擎改走 REST 检索接口(30 次/分钟),
|
|
65
|
+
# 留空则抓公开搜索页(10 次/分钟)。token 只影响额度,无需勾选任何仓库权限。
|
|
66
|
+
github_token = ""
|
|
67
|
+
|
|
52
68
|
# 视觉模型代理:模型引用复用 llm.profiles / models.toml,不在这里存 API Key。
|
|
53
69
|
# models 按顺序作为故障转移链;主模型不支持视觉且此项开启时,图片会先交给这些模型分析。
|
|
54
70
|
# 模型原生视觉在设置页「视觉」按当前模型/渠道开关(models.toml 或 [llm.profiles.<id>] 的 native_vision)。
|
|
@@ -56,49 +72,70 @@ enabled = true
|
|
|
56
72
|
enabled = false
|
|
57
73
|
models = []
|
|
58
74
|
|
|
59
|
-
#
|
|
60
|
-
# TUI /settings → 图像生成 中配置;
|
|
61
|
-
#
|
|
75
|
+
# 图像生成:两条通道按 provider 二选一——OpenAI 兼容 Image API 与 Gemini 的
|
|
76
|
+
# generateContent。接口地址、Key、模型与默认参数在 TUI /settings → 图像生成 中配置;
|
|
77
|
+
# api_key 写入本地 config.toml(不提交仓库)。size/quality 是 OpenAI 通道的口径,
|
|
78
|
+
# aspect_ratio/image_size 是 Gemini 通道的口径,切换服务商不会丢掉另一家的取值。
|
|
62
79
|
[image_gen]
|
|
63
80
|
enabled = false
|
|
81
|
+
# 服务商:openai / gemini。
|
|
82
|
+
provider = "openai"
|
|
83
|
+
# OpenAI 通道:https://api.openai.com/v1;Gemini 通道:
|
|
84
|
+
# https://generativelanguage.googleapis.com/v1beta。
|
|
64
85
|
base_url = "https://api.openai.com/v1"
|
|
65
86
|
api_key = ""
|
|
66
87
|
api_key_env = "OPENAI_API_KEY"
|
|
88
|
+
# OpenAI 通道默认 gpt-image-2;Gemini 通道默认 gemini-2.5-flash-image。
|
|
67
89
|
model = "gpt-image-2"
|
|
90
|
+
# 仅 OpenAI 通道使用:auto 或 宽x高。
|
|
68
91
|
size = "auto"
|
|
92
|
+
# 仅 OpenAI 通道使用:auto / low / medium / high。
|
|
69
93
|
quality = "auto"
|
|
94
|
+
# 仅 OpenAI 通道使用:png / jpeg / webp(Gemini 恒为 png)。
|
|
70
95
|
output_format = "png"
|
|
96
|
+
# 仅 Gemini 通道使用:画面比例,14 种之一(留空由模型取默认)。
|
|
97
|
+
aspect_ratio = ""
|
|
98
|
+
# 仅 Gemini 通道使用:分辨率档位 1K / 2K / 4K(留空由模型取默认)。
|
|
99
|
+
image_size = ""
|
|
71
100
|
n = 1
|
|
72
101
|
timeout_seconds = 120
|
|
73
102
|
|
|
74
|
-
# TTS
|
|
75
|
-
#
|
|
76
|
-
# 2) 本地推理(可选,需构建时带 omnicrawl-tts/onnx feature):MOSS-TTS-Nano ONNX,
|
|
77
|
-
# 首次启用时在 TUI /settings → TTS 中下载模型(约 763MB)并选择音色。
|
|
103
|
+
# TTS 语音合成(云服务):两条通道按 tts_cloud.provider 二选一,本机不需要模型文件
|
|
104
|
+
# 或 espeak-ng 一类原生依赖,只要 HTTP 与密钥。在 TUI /settings → TTS 中配置。
|
|
78
105
|
[tts]
|
|
79
106
|
enabled = false
|
|
80
|
-
model_dir = ""
|
|
81
|
-
voice = "Junhao"
|
|
82
107
|
auto_play = true
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
device = "auto"
|
|
86
|
-
# codec 流式解码(低首字节延迟;默认开启,显存占用低,避免全量解码 OOM)。
|
|
87
|
-
streaming = true
|
|
108
|
+
# 回合结束后朗读助手回复(直接在宿主里合成播放,不经 tts_synthesize 工具、也不走审批)。
|
|
109
|
+
speak_replies = false
|
|
88
110
|
output_dir = ".omnicrawl/.agent_tmp/tts"
|
|
89
111
|
|
|
90
|
-
#
|
|
112
|
+
# 服务商路由:openai / gemini。
|
|
113
|
+
[tts_cloud]
|
|
114
|
+
provider = "openai"
|
|
115
|
+
|
|
116
|
+
# OpenAI 通道(provider = "openai" 时生效):POST {base_url}/audio/speech。
|
|
117
|
+
# api_key 写入本地 config.toml(不提交仓库),也可在设置面板直接填写。
|
|
91
118
|
[tts_api]
|
|
92
|
-
enabled = true
|
|
93
119
|
base_url = "https://api.openai.com/v1"
|
|
94
120
|
api_key = ""
|
|
95
|
-
api_key_env = "OPENAI_API_KEY"
|
|
96
121
|
model = "gpt-4o-mini-tts"
|
|
122
|
+
# 音色:alloy/ash/ballad/coral/echo/fable/nova/onyx/sage/shimmer/verse/marin/cedar。
|
|
97
123
|
voice = "alloy"
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
#
|
|
101
|
-
speed = 1
|
|
124
|
+
# 语气指令;只对 gpt-4o-mini-tts 一类模型生效,留空则不下发。
|
|
125
|
+
instructions = ""
|
|
126
|
+
# 语速(0.25~4.0);1 时不下发该字段。
|
|
127
|
+
speed = 1
|
|
128
|
+
timeout_seconds = 120
|
|
129
|
+
|
|
130
|
+
# Gemini 通道(provider = "gemini" 时生效):POST {base_url}/models/{model}:generateContent。
|
|
131
|
+
[tts_gemini]
|
|
132
|
+
base_url = "https://generativelanguage.googleapis.com/v1beta"
|
|
133
|
+
api_key = ""
|
|
134
|
+
model = "gemini-2.5-flash-preview-tts"
|
|
135
|
+
# 预置音色名(如 Kore、Puck、Zephyr)。
|
|
136
|
+
voice = "Kore"
|
|
137
|
+
# 语气指令:Gemini 没有独立的指令字段,这段文本作为正文前缀下发。
|
|
138
|
+
instructions = ""
|
|
102
139
|
timeout_seconds = 120
|
|
103
140
|
|
|
104
141
|
[approval]
|
|
@@ -116,6 +153,11 @@ show_thinking = true
|
|
|
116
153
|
[memory]
|
|
117
154
|
enabled = true
|
|
118
155
|
|
|
156
|
+
# Skill 额外路径:目录或 .md 文件,相对路径按工作区解析。
|
|
157
|
+
# 与内置作用域一起参与发现,扫描顺序排在最后;同名 Skill 由先扫描到的作用域胜出。
|
|
158
|
+
# [skills]
|
|
159
|
+
# extra_paths = ["./my-skills", "~/.omnicrawl/shared-skills"]
|
|
160
|
+
|
|
119
161
|
# 内置工具开关:默认除 powershell 关闭外,其余全部启用。
|
|
120
162
|
# 只在需要禁用某个工具时取消注释并设为 false。
|
|
121
163
|
# [tools]
|
|
@@ -249,10 +291,10 @@ enabled = false
|
|
|
249
291
|
# model_request_concurrency = 2
|
|
250
292
|
# allow_background = false
|
|
251
293
|
# allow_fork = false
|
|
252
|
-
# allow_worktree =
|
|
253
|
-
# allow_standard_agent =
|
|
294
|
+
# allow_worktree = true
|
|
295
|
+
# allow_standard_agent = true
|
|
254
296
|
# allow_shared_workspace_writes = false
|
|
255
|
-
# enable_verify_agent =
|
|
297
|
+
# enable_verify_agent = true
|
|
256
298
|
# verify_command_timeout_seconds = 120
|
|
257
299
|
# task_retention_minutes = 60
|
|
258
300
|
# result_summary_chars = 6000
|
|
@@ -16,7 +16,7 @@ name = "Jev 主渠道"
|
|
|
16
16
|
# 请求方式:
|
|
17
17
|
# jev —— Jev 原生接口,POST {base_url}/v1/decide,state + questions 直接进请求体。
|
|
18
18
|
# chat_completions —— OpenAI 兼容接口,POST {base_url}/chat/completions,
|
|
19
|
-
# base_url 与 [image_gen]
|
|
19
|
+
# base_url 与 [image_gen] 同口径(填到 /v1);
|
|
20
20
|
# 同一份 state + questions 作为 user 消息发出,从返回内容里解析 answers。
|
|
21
21
|
# onejev —— 本机自部署的 OneJev 服务,POST {base_url}/v1/systemone,
|
|
22
22
|
# 请求与响应形状与 jev 相同;base_url 指向本地服务(见下方 [local])。
|
|
@@ -29,6 +29,10 @@ api_key = ""
|
|
|
29
29
|
api_key_env = "JEV_API_KEY"
|
|
30
30
|
# 决策模型:jev-latest 跟随最新版本;生产环境建议钉死版本(如 jev-1.13.0)。
|
|
31
31
|
model = "jev-latest"
|
|
32
|
+
# 请求失败后的重试次数(不含首次请求):连接失败、超时与上游 5xx 按 0.2s→0.4s→0.8s(封顶 2s)
|
|
33
|
+
# 退避重试这么多次;4xx 不重试(那是请求本身的问题),读不到可用答案也不重试(不猜、不编)。
|
|
34
|
+
# 一次决策在回合路径上同步等待,因此次数要有界(上限 10);0 表示不重试。
|
|
35
|
+
retry_count = 3
|
|
32
36
|
enabled = true
|
|
33
37
|
|
|
34
38
|
# 决策模型的功能开关。开关关闭时相关功能走原路径(当前即对话模型审查与本地排序检索)。
|
|
@@ -47,6 +51,18 @@ memory_search_rerank = false
|
|
|
47
51
|
# 知识库检索使用决策模型排序:对 kb_search 同理,候选描述用标题、路径与摘要。
|
|
48
52
|
kb_search_rerank = false
|
|
49
53
|
|
|
54
|
+
# Skill 检索使用决策模型排序:开启后 skill_search 带关键词检索时先取更宽的候选池(最多 20 条),
|
|
55
|
+
# 再让决策模型按相关程度排序,按各候选项的置信度降序返回,条数仍按调用方给的 max_results。
|
|
56
|
+
# 不带关键词(按作用域列出全部)时没有「相关度」可判,不送审。决策服务不可用时回退本地排序,
|
|
57
|
+
# 检索工具不会因此失败。
|
|
58
|
+
skill_search_rerank = false
|
|
59
|
+
|
|
60
|
+
# 联网搜索使用决策模型排序:开启后 web_search 把结果切成 5 组(每组 10 条)并发交给决策模型
|
|
61
|
+
# 排序,各组取前 5 条拼接后返回,因此单次最多回 25 条(调用方给的 max_results 更小时按它截断)。
|
|
62
|
+
# 分组只用本地关键词覆盖度圈出候选池,组内顺序由决策模型定。决策服务不可用时回退本地顺序,
|
|
63
|
+
# 搜索不会因此失败。
|
|
64
|
+
web_search_rerank = false
|
|
65
|
+
|
|
50
66
|
# 提问由决策模型自动作答:开启后模型调用 ask_user 且带选项时,不再弹面板等你作答,
|
|
51
67
|
# 而是把提问正文、你本回合的请求与本回合已有的顾问答复交给决策模型,让它选最符合
|
|
52
68
|
# 你意图的一项。没有选项的提问(自由问答)照旧交给你。决策服务不可用时回退到人工
|
|
@@ -72,6 +88,34 @@ ask_user_custody = false
|
|
|
72
88
|
# 重启后按同一份投影重建同一份上下文。
|
|
73
89
|
tool_call_prune = false
|
|
74
90
|
|
|
91
|
+
# 新提问自动建议工具:开启后,你每发一条新提问,宿主先把**这一条提问的正文**(不带历史)
|
|
92
|
+
# 与一份「已启用工具」的清单交给决策模型,问它「本次该用哪些工具」。每个工具一个是否题,
|
|
93
|
+
# 概率 ≥0.5 进建议名单、<0.5 排除;另有最后一个兜底判定——本条属于普通对话、寒暄或无从
|
|
94
|
+
# 判断的异常任务时,整份建议作废(不插入任何内容)。名单非空时在提问尾部追加一行
|
|
95
|
+
# 「当前请求建议调用tool:a、b。」再发给模型。
|
|
96
|
+
#
|
|
97
|
+
# 清单就是本机工具表里全部已启用的工具(每项为工具名 + 描述首句,截断到 24 字),不做人工
|
|
98
|
+
# 取舍:关掉的工具与当前没有运行期的工具(语音合成、顾问、Windows 组)本来就不在表里。
|
|
99
|
+
# 该用专用工具还是顺手用泛化工具,由决策模型按选择要求判断——子代理、生图、语音、桌面
|
|
100
|
+
# 操作这类专用工具贴合任务特殊性;读文件、搜代码、执行命令、记待办这类通用工具几乎每轮
|
|
101
|
+
# 都能顺手用一下,除非本次任务明确围绕它展开,否则判为用不到。决策服务不可用(缺渠道/凭据、
|
|
102
|
+
# 超时、响应不可解析)时按「本次无建议」处理,提问原文照发,不会挡住你的输入。
|
|
103
|
+
tool_suggestion = false
|
|
104
|
+
|
|
105
|
+
# 新提问自动建议子代理:与上面的 `tool_suggestion` 形状相同、互相独立,可单开也可同时开。
|
|
106
|
+
# 区别在候选清单:这里送审的是**本机可调度的子代理角色**(每项为角色名 + 角色描述首句,
|
|
107
|
+
# 截断到 24 字),与 `subagent` 工具的参数枚举同源,内核不能调度的角色不会进审。
|
|
108
|
+
# 判定同样是每个角色一个是否题 + 一个兜底判定(本条是否属于无需任何子代理的普通对话),
|
|
109
|
+
# 概率阈值都是 0.5。名单非空时在提问尾部追加一行「当前请求建议使用子代理:explore、plan。」。
|
|
110
|
+
#
|
|
111
|
+
# 子代理个个都是一次带独立上下文的子回合,开销明显高于主模型自己动手,因此判定要求特别
|
|
112
|
+
# 强调:只有能拆成相互独立、可并行推进的子任务,或需要隔离视角核查与隔离改动时才算用得到;
|
|
113
|
+
# 单步问答、小改动、串行依赖的活一律判为用不到。
|
|
114
|
+
#
|
|
115
|
+
# 两类同时开启时,两条请求**并发发起**,总耗时取两者的较大者而不是相加;任一失败只少一行。
|
|
116
|
+
# 决策服务不可用时同样按「本次无建议」处理,提问原文照发。
|
|
117
|
+
subagent_suggestion = false
|
|
118
|
+
|
|
75
119
|
# 决策 REST 接口:把决策能力暴露给本机其它程序(CLI 工具、Skill、外部脚本),
|
|
76
120
|
# 它们不必经主模型就能拿到决策结果。大脑(主模型)负责规划,小脑(决策模型)负责
|
|
77
121
|
# 快速判定,两者通过这个接口协同。
|
|
@@ -91,14 +135,15 @@ tool_call_prune = false
|
|
|
91
135
|
[api]
|
|
92
136
|
enabled = false
|
|
93
137
|
host = "127.0.0.1"
|
|
94
|
-
# 端口:与本地 API(8765
|
|
138
|
+
# 端口:与本地 API(8765)错开。
|
|
95
139
|
port = 8767
|
|
96
140
|
|
|
97
141
|
# 自部署(OneJev):在本机跑一份决策模型,不依赖云端服务。
|
|
98
142
|
#
|
|
99
143
|
# 官方发布 0.8B / 4B / 9B / 27B / 27B-FP8 五档(Hugging Face 的 OmniJev/OneJev-*),
|
|
100
|
-
#
|
|
101
|
-
#
|
|
144
|
+
# 由本机的判定 worker 直接驱动引擎,经命名管道(Windows)/ Unix socket 提供同一套判定——
|
|
145
|
+
# 不经 TCP 与 HTTP。设置面板的「结构化决策模型」页可以一键下载某一档、准备运行环境、
|
|
146
|
+
# 启停本地服务,并把决策渠道切到自部署。
|
|
102
147
|
# 权重与虚拟环境都在 model_dir 下(默认 ~/.omnicrawl/onejev),不进工作区。
|
|
103
148
|
[local]
|
|
104
149
|
# 当前部署的尺寸:0.8B / 4B / 9B / 27B-FP8 / 27B。
|
|
@@ -113,5 +158,5 @@ model_dir = ""
|
|
|
113
158
|
# 本机已有服务在跑(本机所有实例共用一份)就直接复用,不会重复启动;服务常驻,
|
|
114
159
|
# 不随实例退出被回收,要停就在设置页点「停止服务」。
|
|
115
160
|
auto_start = true
|
|
116
|
-
#
|
|
161
|
+
# 启动后等待服务就绪的上限(秒):首次加载权重并捕获 CUDA 图,大尺寸会久一些。
|
|
117
162
|
health_timeout_seconds = 600
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
# OmniCrawl 子代理设置模板(首次启动自动复制为 subagents.toml)
|
|
2
|
-
# 子代理设置已从 config.toml 完全迁移到本文件;程序只读取 subagents.toml,
|
|
3
|
-
# 不再回退读取 config.toml 的 [subagents] 段。
|
|
4
|
-
version = 1
|
|
5
|
-
|
|
6
|
-
# SubAgent
|
|
7
|
-
[subagents]
|
|
8
|
-
enabled = false
|
|
9
|
-
max_depth = 1
|
|
10
|
-
max_concurrency = 2
|
|
11
|
-
max_tasks_per_batch = 4
|
|
12
|
-
default_timeout_seconds = 3600
|
|
13
|
-
model_request_concurrency = 2
|
|
14
|
-
allow_background = false
|
|
15
|
-
# 仅开放已脱敏父公开上下文的 read_only/verify Fork;默认关闭。
|
|
16
|
-
allow_fork = false
|
|
17
|
-
allow_shared_workspace_writes = false
|
|
18
|
-
allow_worktree =
|
|
19
|
-
allow_standard_agent =
|
|
20
|
-
enable_verify_agent =
|
|
21
|
-
verify_command_timeout_seconds = 120
|
|
22
|
-
task_retention_minutes = 60
|
|
23
|
-
result_summary_chars = 6000
|
|
24
|
-
|
|
25
|
-
# 单独设置每个子代理使用的模型;key 必须与 Agent 定义名一致(如 explore/plan/verify)。
|
|
26
|
-
# model 支持:models.toml key/alias、profile/model_id 或裸 model_id;
|
|
27
|
-
# 缺省或写 inherit 表示沿用父模型(与任务级 model=inherit 语义一致)。
|
|
28
|
-
[subagents.models.explore]
|
|
29
|
-
# model = "default-chat"
|
|
30
|
-
|
|
31
|
-
[subagents.models.plan]
|
|
32
|
-
# model = "default-chat"
|
|
33
|
-
|
|
34
|
-
[subagents.models.verify]
|
|
35
|
-
# model = "default-chat"
|
|
36
|
-
|
|
37
|
-
[subagents.models.general-purpose]
|
|
38
|
-
# model = "default-chat"
|
|
1
|
+
# OmniCrawl 子代理设置模板(首次启动自动复制为 subagents.toml)
|
|
2
|
+
# 子代理设置已从 config.toml 完全迁移到本文件;程序只读取 subagents.toml,
|
|
3
|
+
# 不再回退读取 config.toml 的 [subagents] 段。
|
|
4
|
+
version = 1
|
|
5
|
+
|
|
6
|
+
# SubAgent 全局边界:默认关闭;开启后三种角色的能力开关默认放开(verify / standard / worktree)。
|
|
7
|
+
[subagents]
|
|
8
|
+
enabled = false
|
|
9
|
+
max_depth = 1
|
|
10
|
+
max_concurrency = 2
|
|
11
|
+
max_tasks_per_batch = 4
|
|
12
|
+
default_timeout_seconds = 3600
|
|
13
|
+
model_request_concurrency = 2
|
|
14
|
+
allow_background = false
|
|
15
|
+
# 仅开放已脱敏父公开上下文的 read_only/verify Fork;默认关闭。
|
|
16
|
+
allow_fork = false
|
|
17
|
+
allow_shared_workspace_writes = false
|
|
18
|
+
allow_worktree = true
|
|
19
|
+
allow_standard_agent = true
|
|
20
|
+
enable_verify_agent = true
|
|
21
|
+
verify_command_timeout_seconds = 120
|
|
22
|
+
task_retention_minutes = 60
|
|
23
|
+
result_summary_chars = 6000
|
|
24
|
+
|
|
25
|
+
# 单独设置每个子代理使用的模型;key 必须与 Agent 定义名一致(如 explore/plan/verify)。
|
|
26
|
+
# model 支持:models.toml key/alias、profile/model_id 或裸 model_id;
|
|
27
|
+
# 缺省或写 inherit 表示沿用父模型(与任务级 model=inherit 语义一致)。
|
|
28
|
+
[subagents.models.explore]
|
|
29
|
+
# model = "default-chat"
|
|
30
|
+
|
|
31
|
+
[subagents.models.plan]
|
|
32
|
+
# model = "default-chat"
|
|
33
|
+
|
|
34
|
+
[subagents.models.verify]
|
|
35
|
+
# model = "default-chat"
|
|
36
|
+
|
|
37
|
+
[subagents.models.general-purpose]
|
|
38
|
+
# model = "default-chat"
|
|
@@ -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()
|
|
@@ -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/package.json
CHANGED
package/version.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnicrawl/cli-win32-ia32",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"os": "win32",
|
|
5
5
|
"cpu": "ia32",
|
|
6
6
|
"kernelVersion": "0.0.1",
|
|
7
|
-
"hostVersion": "0.
|
|
7
|
+
"hostVersion": "0.3.0",
|
|
8
8
|
"hostPlatform": "win32-ia32",
|
|
9
|
-
"builtAt": "2026-10-
|
|
9
|
+
"builtAt": "2026-10-18T23:19:33.374Z"
|
|
10
10
|
}
|