@omnicrawl/cli-win32-ia32 0.4.2 → 0.4.4

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.
Files changed (31) hide show
  1. package/bin/omnicrawl.exe +0 -0
  2. package/host/omnicrawl/config/templates/config.example.toml +49 -0
  3. package/host/omnicrawl/config/templates/decision_models.example.toml +13 -2
  4. package/host/omnicrawl/templates/plan.md +1 -1
  5. package/host/omnicrawl/templates/subagents/explore.md +5 -1
  6. package/host/omnicrawl/templates/subagents/general-purpose.md +6 -2
  7. package/host/omnicrawl/templates/subagents/plan.md +5 -1
  8. package/host/omnicrawl/templates/subagents/review.md +5 -1
  9. package/host/omnicrawl/templates/subagents/verify.md +5 -1
  10. package/host/omnicrawl/templates/system_prompt.md +1 -0
  11. package/host/omnicrawl-api.exe +0 -0
  12. package/host/omnicrawl-decision.exe +0 -0
  13. package/host/omnicrawl-fetch.exe +0 -0
  14. package/host/omnicrawl-host.exe +0 -0
  15. package/host/omnicrawl-mcp-server.exe +0 -0
  16. package/host/omnicrawl-trigger.exe +0 -0
  17. package/host/omnicrawl-tui.exe +0 -0
  18. package/host/omnicrawl.exe +0 -0
  19. package/host/rust/assets/config-templates/config.example.toml +49 -0
  20. package/host/rust/assets/config-templates/decision_models.example.toml +13 -2
  21. package/host/rust/assets/templates/plan.md +1 -1
  22. package/host/rust/assets/templates/subagents/explore.md +5 -1
  23. package/host/rust/assets/templates/subagents/general-purpose.md +6 -2
  24. package/host/rust/assets/templates/subagents/plan.md +5 -1
  25. package/host/rust/assets/templates/subagents/review.md +5 -1
  26. package/host/rust/assets/templates/subagents/verify.md +5 -1
  27. package/host/rust/assets/templates/system_prompt.md +1 -0
  28. package/package.json +2 -1
  29. package/version.json +4 -4
  30. package/host/omnicrawl/extensions/node_runner.mjs +0 -402
  31. package/host/rust/assets/extensions/node_runner.mjs +0 -402
package/bin/omnicrawl.exe CHANGED
Binary file
@@ -297,6 +297,55 @@ impersonate = ""
297
297
  # 单次请求的 URL 上限:0 表示用默认上限 20,否则按 1–20 收窄。
298
298
  max_urls = 0
299
299
 
300
+ # 触发接口:让外部组件(定时任务、传感器脚本、CI、监控告警)在自定条件成立时拉起一个
301
+ # 独立 loop。它们自己判断时间/温度/湿度/构建状态,命中后 POST 一条自定义消息,OmniCrawl
302
+ # 用一次独立会话跑一轮,跑完把结果落盘并按需投递到消息平台。
303
+ #
304
+ # omnicrawl trigger ensure # 确保本机有一份可用服务(已就绪则复用)
305
+ # omnicrawl trigger status # 打印配置、并发与队列
306
+ # omnicrawl trigger stop # 停止本机共用的服务
307
+ #
308
+ # POST /v1/triggers {message, data?, title?, session_key?, workspace?,
309
+ # timeout_seconds?, wait_seconds?, idempotency_key?,
310
+ # notify?, tags?}
311
+ # → {trigger_id, state, title, created_at, queue_position?, duplicate}
312
+ # GET /v1/triggers[?state=&tag=&limit=] 列表(按创建时间倒序)
313
+ # GET /v1/triggers/{id} 单条状态与结果
314
+ # GET /v1/triggers/{id}/events SSE,事件名与 /api/v1/runs 同源
315
+ # GET /v1/triggers/{id}/transcript 会话转录(回看那轮做了什么)
316
+ # POST /v1/triggers/{id}/cancel 取消(幂等)
317
+ #
318
+ # 与 /api/v1/runs 的区别:那条路是交互式入口,占单活动生成名额且写当前会话;触发接口有
319
+ # 自己的会话根与并发池,无人值守任务不会和用户抢名额,也不污染用户的对话历史。
320
+ #
321
+ # 条件判定完全在外部组件:这里不内置调度器、不解析 cron。
322
+ # 接口不做鉴权,host 只允许回环地址;工具白名单默认只读 + 记忆/知识库 + 联网检索,
323
+ # 审批固定为自动审查(危险调用仍被审查闸拦下)。默认关闭。
324
+ [trigger_api]
325
+ enabled = false
326
+ host = "127.0.0.1"
327
+ # 端口:与本地 API(8765)、决策接口(8767)、抓取接口(8768)错开。
328
+ port = 8769
329
+ # 同时最多跑几个 loop(1–4)。每个 slot 一套独立的内核进程、工具表与会话。
330
+ max_concurrency = 1
331
+ # 排队上限;满了返回 429 QUEUE_FULL,调用方稍后重试。
332
+ queue_limit = 32
333
+ # 单次回合上限(60–7200 秒);请求可用 timeout_seconds 收窄。
334
+ default_timeout_seconds = 900
335
+ # per_trigger:每次触发新开会话;keyed:允许请求用 session_key 复用持久会话(同名串行)。
336
+ session_mode = "per_trigger"
337
+ # 请求未给 workspace 时的缺省工作区;留空表示用宿主当前工作区。
338
+ default_workspace = ""
339
+ # 无人值守工具白名单(白名单语义:表外的工具既不声明给模型,宿主也不执行)。
340
+ # 要放开 bash / write_file,前提是调用来源可信——报文、日志与网页内容会进模型上下文。
341
+ tools = ["read", "read_image", "list", "find", "grep", "memory_search", "memory_read",
342
+ "memory_expand_related", "memory_write", "kb_search", "kb_read", "web_search",
343
+ "fetcher"]
344
+ # 追加到固定无人值守约束之后的部署专属提示(例如「回复用中文,不超过 300 字」)。
345
+ system_prompt_append = ""
346
+ # 触发记录在服务进程内的保留小时数(1–8760);会话转录另行保留在会话目录里。
347
+ retention_hours = 168
348
+
300
349
  [agent_temp]
301
350
  enabled = true
302
351
  directory = ".omnicrawl/.agent_tmp"
@@ -118,10 +118,21 @@ tool_suggestion = false
118
118
  # 强调:只有能拆成相互独立、可并行推进的子任务,或需要隔离视角核查与隔离改动时才算用得到;
119
119
  # 单步问答、小改动、串行依赖的活一律判为用不到。
120
120
  #
121
- # 两类同时开启时,两条请求**并发发起**,总耗时取两者的较大者而不是相加;任一失败只少一行。
122
- # 决策服务不可用时同样按「本次无建议」处理,提问原文照发。
121
+ # 三条建议(工具 / 子代理 / Skill)可同时开启:每条请求**并发发起**,总耗时取最大者而不是
122
+ # 相加;任一失败只少一行。决策服务不可用时同样按「本次无建议」处理,提问原文照发。
123
123
  subagent_suggestion = false
124
124
 
125
+ # 新提问自动建议 Skill:同样独立、可单开也可与前两条同开。候选另走一条路——先用当前提问对
126
+ # 本机 Skill 索引做 BM25 检索(检索范围是名称 + 描述 + 正文),挑出最多 8 条池子,再把池子
127
+ # 交给决策模型判定「本次符合哪几个的适用场景」。
128
+ #
129
+ # 上面的 `skill_search_rerank` 开着时,池子会先用决策模型重排再送审;关着就用 BM25 名次。
130
+ # 也就是说注入本身不依赖决策服务,只有「判定哪两个最合适」这一步需要它。
131
+ #
132
+ # 最终**只注入前两名**,在提问尾部追加一行「建议使用skills:pdf、docx。」。
133
+ # 本机一个 Skill 都没装、或提问一个检索词都没命中时,这次建议直接跳过(不发请求)。
134
+ skill_suggestion = false
135
+
125
136
  # 决策 REST 接口:把决策能力暴露给本机其它程序(CLI 工具、Skill、外部脚本),
126
137
  # 它们不必经主模型就能拿到决策结果。大脑(主模型)负责规划,小脑(决策模型)负责
127
138
  # 快速判定,两者通过这个接口协同。
@@ -51,7 +51,7 @@
51
51
  - 方案结构和各阶段之间的数据流;
52
52
  - 文件/模块职责与必要的接口、类型、配置或数据变更;
53
53
  - 输入校验、权限、敏感信息、错误、超时、取消和重试处理;
54
- - 与现有行为、历史数据、客户端、Provider、插件或外部依赖的兼容性;
54
+ - 与现有行为、历史数据、客户端、Provider 或外部依赖的兼容性;
55
55
  - 最小而有针对性的测试案例、验收条件和失败后的诊断路径;
56
56
  - 发布、迁移、部署、监控与回滚影响(确有影响时才展开)。
57
57
 
@@ -2,7 +2,11 @@
2
2
  name: explore
3
3
  description: 快速只读探索代码、网页和外部能力,返回文件路径、行号与可复核证据
4
4
  disallowedTools:
5
- - subagent
5
+ - subagent_run
6
+ - subagent_list
7
+ - subagent_cancel
8
+ - subagent_apply_worktree
9
+ - subagent_discard_worktree
6
10
  - Edit_file
7
11
  - write_file
8
12
  - memory_write
@@ -15,7 +15,11 @@ tools:
15
15
  - bash
16
16
  - powershell
17
17
  disallowedTools:
18
- - subagent
18
+ - subagent_run
19
+ - subagent_list
20
+ - subagent_cancel
21
+ - subagent_apply_worktree
22
+ - subagent_discard_worktree
19
23
  - switch_model
20
24
  - switch_skill
21
25
  - load_skill
@@ -36,7 +40,7 @@ background: false
36
40
  5. 完成后用简洁中文总结:改了什么、为什么、如何验证、残留风险。
37
41
 
38
42
  约束:
39
- - 禁止调用 subagent、switch_model、switch_skill、load_skill、search_skills。
43
+ - 禁止调用 subagent_run、subagent_list、subagent_cancel、subagent_apply_worktree、subagent_discard_worktree、switch_model、switch_skill、load_skill、search_skills。
40
44
  - 不要提交 git commit、push 或改远程。
41
45
  - 不要删除用户未明确要求删除的文件。
42
46
  - isolation=worktree 时,结果会以分支/diff 形式返回父 Agent,由父决定是否应用。
@@ -2,7 +2,11 @@
2
2
  name: plan
3
3
  description: 结合项目与外部证据制定只读的软件架构、实施步骤、风险与验证计划
4
4
  disallowedTools:
5
- - subagent
5
+ - subagent_run
6
+ - subagent_list
7
+ - subagent_cancel
8
+ - subagent_apply_worktree
9
+ - subagent_discard_worktree
6
10
  - Edit_file
7
11
  - write_file
8
12
  - memory_write
@@ -9,7 +9,11 @@ tools:
9
9
  - grep
10
10
  - git
11
11
  disallowedTools:
12
- - subagent
12
+ - subagent_run
13
+ - subagent_list
14
+ - subagent_cancel
15
+ - subagent_apply_worktree
16
+ - subagent_discard_worktree
13
17
  - Edit_file
14
18
  - write_file
15
19
  - memory_write
@@ -12,7 +12,11 @@ tools:
12
12
  - memory_expand_related
13
13
  - verify_command
14
14
  disallowedTools:
15
- - subagent
15
+ - subagent_run
16
+ - subagent_list
17
+ - subagent_cancel
18
+ - subagent_apply_worktree
19
+ - subagent_discard_worktree
16
20
  - Edit_file
17
21
  - write_file
18
22
  - bash
@@ -64,6 +64,7 @@ omnicrawl文档(共 15 篇,均以 `omnicrawl://docs/<文件名>` 读取,
64
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`。
65
65
  - 决策接口(结构化决策模型的本地 REST 服务:启动、无鉴权的回环访问、decide/choice/rank/review 端点、常驻与复用、脱敏与安全边界):读取 `omnicrawl://docs/decision_api.md`;实现位于 `rust/crates/omnicrawl-decision/` 和 `rust/crates/omnicrawl-config/src/features/decision_model.rs`。
66
66
  - 抓取接口(fetcher 的本机 REST 通道:`[fetch_api]` 配置、`/v1/fetch` 端点、无鉴权回环访问、Python 批量脚本调用、与工具同源的抓取语义与安全边界):读取 `omnicrawl://docs/fetch_api.md`;实现位于 `rust/crates/omnicrawl-fetch/`、`rust/crates/omnicrawl-config/src/features/fetch_api.rs` 和 `rust/crates/omnicrawl-host/src/tools/fetcher.rs`。
67
+ - 触发接口(外部事件拉起独立 loop:`[trigger_api]` 配置、`/v1/triggers` 端点、条件判定留给外部组件、无人值守审批与工具白名单、终态投递与去重):读取 `omnicrawl://docs/trigger_api.md`;实现位于 `rust/crates/omnicrawl-trigger/` 和 `rust/crates/omnicrawl-config/src/features/trigger_api.rs`。
67
68
  - FSAPP 接入(文件系统应用侧的接入约定):读取 `omnicrawl://docs/FSAPP.md`。
68
69
 
69
70
  脱敏占位符引用规则:
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -297,6 +297,55 @@ impersonate = ""
297
297
  # 单次请求的 URL 上限:0 表示用默认上限 20,否则按 1–20 收窄。
298
298
  max_urls = 0
299
299
 
300
+ # 触发接口:让外部组件(定时任务、传感器脚本、CI、监控告警)在自定条件成立时拉起一个
301
+ # 独立 loop。它们自己判断时间/温度/湿度/构建状态,命中后 POST 一条自定义消息,OmniCrawl
302
+ # 用一次独立会话跑一轮,跑完把结果落盘并按需投递到消息平台。
303
+ #
304
+ # omnicrawl trigger ensure # 确保本机有一份可用服务(已就绪则复用)
305
+ # omnicrawl trigger status # 打印配置、并发与队列
306
+ # omnicrawl trigger stop # 停止本机共用的服务
307
+ #
308
+ # POST /v1/triggers {message, data?, title?, session_key?, workspace?,
309
+ # timeout_seconds?, wait_seconds?, idempotency_key?,
310
+ # notify?, tags?}
311
+ # → {trigger_id, state, title, created_at, queue_position?, duplicate}
312
+ # GET /v1/triggers[?state=&tag=&limit=] 列表(按创建时间倒序)
313
+ # GET /v1/triggers/{id} 单条状态与结果
314
+ # GET /v1/triggers/{id}/events SSE,事件名与 /api/v1/runs 同源
315
+ # GET /v1/triggers/{id}/transcript 会话转录(回看那轮做了什么)
316
+ # POST /v1/triggers/{id}/cancel 取消(幂等)
317
+ #
318
+ # 与 /api/v1/runs 的区别:那条路是交互式入口,占单活动生成名额且写当前会话;触发接口有
319
+ # 自己的会话根与并发池,无人值守任务不会和用户抢名额,也不污染用户的对话历史。
320
+ #
321
+ # 条件判定完全在外部组件:这里不内置调度器、不解析 cron。
322
+ # 接口不做鉴权,host 只允许回环地址;工具白名单默认只读 + 记忆/知识库 + 联网检索,
323
+ # 审批固定为自动审查(危险调用仍被审查闸拦下)。默认关闭。
324
+ [trigger_api]
325
+ enabled = false
326
+ host = "127.0.0.1"
327
+ # 端口:与本地 API(8765)、决策接口(8767)、抓取接口(8768)错开。
328
+ port = 8769
329
+ # 同时最多跑几个 loop(1–4)。每个 slot 一套独立的内核进程、工具表与会话。
330
+ max_concurrency = 1
331
+ # 排队上限;满了返回 429 QUEUE_FULL,调用方稍后重试。
332
+ queue_limit = 32
333
+ # 单次回合上限(60–7200 秒);请求可用 timeout_seconds 收窄。
334
+ default_timeout_seconds = 900
335
+ # per_trigger:每次触发新开会话;keyed:允许请求用 session_key 复用持久会话(同名串行)。
336
+ session_mode = "per_trigger"
337
+ # 请求未给 workspace 时的缺省工作区;留空表示用宿主当前工作区。
338
+ default_workspace = ""
339
+ # 无人值守工具白名单(白名单语义:表外的工具既不声明给模型,宿主也不执行)。
340
+ # 要放开 bash / write_file,前提是调用来源可信——报文、日志与网页内容会进模型上下文。
341
+ tools = ["read", "read_image", "list", "find", "grep", "memory_search", "memory_read",
342
+ "memory_expand_related", "memory_write", "kb_search", "kb_read", "web_search",
343
+ "fetcher"]
344
+ # 追加到固定无人值守约束之后的部署专属提示(例如「回复用中文,不超过 300 字」)。
345
+ system_prompt_append = ""
346
+ # 触发记录在服务进程内的保留小时数(1–8760);会话转录另行保留在会话目录里。
347
+ retention_hours = 168
348
+
300
349
  [agent_temp]
301
350
  enabled = true
302
351
  directory = ".omnicrawl/.agent_tmp"
@@ -118,10 +118,21 @@ tool_suggestion = false
118
118
  # 强调:只有能拆成相互独立、可并行推进的子任务,或需要隔离视角核查与隔离改动时才算用得到;
119
119
  # 单步问答、小改动、串行依赖的活一律判为用不到。
120
120
  #
121
- # 两类同时开启时,两条请求**并发发起**,总耗时取两者的较大者而不是相加;任一失败只少一行。
122
- # 决策服务不可用时同样按「本次无建议」处理,提问原文照发。
121
+ # 三条建议(工具 / 子代理 / Skill)可同时开启:每条请求**并发发起**,总耗时取最大者而不是
122
+ # 相加;任一失败只少一行。决策服务不可用时同样按「本次无建议」处理,提问原文照发。
123
123
  subagent_suggestion = false
124
124
 
125
+ # 新提问自动建议 Skill:同样独立、可单开也可与前两条同开。候选另走一条路——先用当前提问对
126
+ # 本机 Skill 索引做 BM25 检索(检索范围是名称 + 描述 + 正文),挑出最多 8 条池子,再把池子
127
+ # 交给决策模型判定「本次符合哪几个的适用场景」。
128
+ #
129
+ # 上面的 `skill_search_rerank` 开着时,池子会先用决策模型重排再送审;关着就用 BM25 名次。
130
+ # 也就是说注入本身不依赖决策服务,只有「判定哪两个最合适」这一步需要它。
131
+ #
132
+ # 最终**只注入前两名**,在提问尾部追加一行「建议使用skills:pdf、docx。」。
133
+ # 本机一个 Skill 都没装、或提问一个检索词都没命中时,这次建议直接跳过(不发请求)。
134
+ skill_suggestion = false
135
+
125
136
  # 决策 REST 接口:把决策能力暴露给本机其它程序(CLI 工具、Skill、外部脚本),
126
137
  # 它们不必经主模型就能拿到决策结果。大脑(主模型)负责规划,小脑(决策模型)负责
127
138
  # 快速判定,两者通过这个接口协同。
@@ -51,7 +51,7 @@
51
51
  - 方案结构和各阶段之间的数据流;
52
52
  - 文件/模块职责与必要的接口、类型、配置或数据变更;
53
53
  - 输入校验、权限、敏感信息、错误、超时、取消和重试处理;
54
- - 与现有行为、历史数据、客户端、Provider、插件或外部依赖的兼容性;
54
+ - 与现有行为、历史数据、客户端、Provider 或外部依赖的兼容性;
55
55
  - 最小而有针对性的测试案例、验收条件和失败后的诊断路径;
56
56
  - 发布、迁移、部署、监控与回滚影响(确有影响时才展开)。
57
57
 
@@ -2,7 +2,11 @@
2
2
  name: explore
3
3
  description: 快速只读探索代码、网页和外部能力,返回文件路径、行号与可复核证据
4
4
  disallowedTools:
5
- - subagent
5
+ - subagent_run
6
+ - subagent_list
7
+ - subagent_cancel
8
+ - subagent_apply_worktree
9
+ - subagent_discard_worktree
6
10
  - Edit_file
7
11
  - write_file
8
12
  - memory_write
@@ -15,7 +15,11 @@ tools:
15
15
  - bash
16
16
  - powershell
17
17
  disallowedTools:
18
- - subagent
18
+ - subagent_run
19
+ - subagent_list
20
+ - subagent_cancel
21
+ - subagent_apply_worktree
22
+ - subagent_discard_worktree
19
23
  - switch_model
20
24
  - switch_skill
21
25
  - load_skill
@@ -36,7 +40,7 @@ background: false
36
40
  5. 完成后用简洁中文总结:改了什么、为什么、如何验证、残留风险。
37
41
 
38
42
  约束:
39
- - 禁止调用 subagent、switch_model、switch_skill、load_skill、search_skills。
43
+ - 禁止调用 subagent_run、subagent_list、subagent_cancel、subagent_apply_worktree、subagent_discard_worktree、switch_model、switch_skill、load_skill、search_skills。
40
44
  - 不要提交 git commit、push 或改远程。
41
45
  - 不要删除用户未明确要求删除的文件。
42
46
  - isolation=worktree 时,结果会以分支/diff 形式返回父 Agent,由父决定是否应用。
@@ -2,7 +2,11 @@
2
2
  name: plan
3
3
  description: 结合项目与外部证据制定只读的软件架构、实施步骤、风险与验证计划
4
4
  disallowedTools:
5
- - subagent
5
+ - subagent_run
6
+ - subagent_list
7
+ - subagent_cancel
8
+ - subagent_apply_worktree
9
+ - subagent_discard_worktree
6
10
  - Edit_file
7
11
  - write_file
8
12
  - memory_write
@@ -9,7 +9,11 @@ tools:
9
9
  - grep
10
10
  - git
11
11
  disallowedTools:
12
- - subagent
12
+ - subagent_run
13
+ - subagent_list
14
+ - subagent_cancel
15
+ - subagent_apply_worktree
16
+ - subagent_discard_worktree
13
17
  - Edit_file
14
18
  - write_file
15
19
  - memory_write
@@ -12,7 +12,11 @@ tools:
12
12
  - memory_expand_related
13
13
  - verify_command
14
14
  disallowedTools:
15
- - subagent
15
+ - subagent_run
16
+ - subagent_list
17
+ - subagent_cancel
18
+ - subagent_apply_worktree
19
+ - subagent_discard_worktree
16
20
  - Edit_file
17
21
  - write_file
18
22
  - bash
@@ -64,6 +64,7 @@ omnicrawl文档(共 15 篇,均以 `omnicrawl://docs/<文件名>` 读取,
64
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`。
65
65
  - 决策接口(结构化决策模型的本地 REST 服务:启动、无鉴权的回环访问、decide/choice/rank/review 端点、常驻与复用、脱敏与安全边界):读取 `omnicrawl://docs/decision_api.md`;实现位于 `rust/crates/omnicrawl-decision/` 和 `rust/crates/omnicrawl-config/src/features/decision_model.rs`。
66
66
  - 抓取接口(fetcher 的本机 REST 通道:`[fetch_api]` 配置、`/v1/fetch` 端点、无鉴权回环访问、Python 批量脚本调用、与工具同源的抓取语义与安全边界):读取 `omnicrawl://docs/fetch_api.md`;实现位于 `rust/crates/omnicrawl-fetch/`、`rust/crates/omnicrawl-config/src/features/fetch_api.rs` 和 `rust/crates/omnicrawl-host/src/tools/fetcher.rs`。
67
+ - 触发接口(外部事件拉起独立 loop:`[trigger_api]` 配置、`/v1/triggers` 端点、条件判定留给外部组件、无人值守审批与工具白名单、终态投递与去重):读取 `omnicrawl://docs/trigger_api.md`;实现位于 `rust/crates/omnicrawl-trigger/` 和 `rust/crates/omnicrawl-config/src/features/trigger_api.rs`。
67
68
  - FSAPP 接入(文件系统应用侧的接入约定):读取 `omnicrawl://docs/FSAPP.md`。
68
69
 
69
70
  脱敏占位符引用规则:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnicrawl/cli-win32-ia32",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "OmniCrawl 平台包(Windows 32 位:内核 + 完整宿主)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,7 @@
21
21
  "omnicrawl-api": "host/omnicrawl-api.exe",
22
22
  "omnicrawl-decision": "host/omnicrawl-decision.exe",
23
23
  "omnicrawl-fetch": "host/omnicrawl-fetch.exe",
24
+ "omnicrawl-trigger": "host/omnicrawl-trigger.exe",
24
25
  "omnicrawl-mcp-server": "host/omnicrawl-mcp-server.exe"
25
26
  },
26
27
  "files": [
package/version.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@omnicrawl/cli-win32-ia32",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "os": "win32",
5
5
  "cpu": "ia32",
6
- "kernelVersion": "0.4.2",
7
- "hostVersion": "0.4.2",
6
+ "kernelVersion": "0.4.4",
7
+ "hostVersion": "0.4.4",
8
8
  "hostPlatform": "win32-ia32",
9
- "builtAt": "2026-10-29T17:12:12.814Z"
9
+ "builtAt": "2026-11-02T05:31:21.240Z"
10
10
  }
@@ -1,402 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * OmniCrawl 单插件 Node runner。
4
- *
5
- * Host 固定调用:
6
- * node node_runner.mjs --plugin-root <canonical-path>
7
- *
8
- * 协议:stdin/stdout NDJSON JSON-RPC 2.0。
9
- * 插件日志经 SDK logger 走 stderr 结构化行;普通 console 也被重定向到 stderr。
10
- */
11
-
12
- import { createRequire } from "node:module";
13
- import path from "node:path";
14
- import process from "node:process";
15
- import { pathToFileURL } from "node:url";
16
- import readline from "node:readline";
17
- import fs from "node:fs";
18
-
19
- const require = createRequire(import.meta.url);
20
-
21
- function parseArgs(argv) {
22
- const args = { pluginRoot: "" };
23
- for (let i = 0; i < argv.length; i += 1) {
24
- const item = argv[i];
25
- if (item === "--plugin-root") {
26
- args.pluginRoot = argv[i + 1] || "";
27
- i += 1;
28
- }
29
- }
30
- return args;
31
- }
32
-
33
- function writeStdout(message) {
34
- process.stdout.write(`${JSON.stringify(message)}\n`);
35
- }
36
-
37
- function writeStderrLine(level, message, fields = {}) {
38
- const payload = {
39
- level,
40
- message: String(message),
41
- fields,
42
- ts: new Date().toISOString(),
43
- };
44
- process.stderr.write(`${JSON.stringify(payload)}\n`);
45
- }
46
-
47
- // 强制把 console 导向 stderr,避免污染协议 stdout。
48
- const originalConsole = { ...console };
49
- for (const level of ["log", "info", "warn", "error", "debug"]) {
50
- console[level] = (...parts) => {
51
- writeStderrLine(level === "log" ? "info" : level, parts.map(String).join(" "));
52
- };
53
- }
54
-
55
- function createLogger() {
56
- return {
57
- debug(message, fields) {
58
- writeStderrLine("debug", message, fields || {});
59
- },
60
- info(message, fields) {
61
- writeStderrLine("info", message, fields || {});
62
- },
63
- warn(message, fields) {
64
- writeStderrLine("warn", message, fields || {});
65
- },
66
- error(message, fields) {
67
- writeStderrLine("error", message, fields || {});
68
- },
69
- };
70
- }
71
-
72
- function definePlugin(plugin) {
73
- return plugin;
74
- }
75
-
76
- function loadPackageJson(pluginRoot) {
77
- const packagePath = path.join(pluginRoot, "package.json");
78
- const raw = fs.readFileSync(packagePath, "utf8");
79
- return JSON.parse(raw);
80
- }
81
-
82
- async function importPlugin(entryPath) {
83
- const url = pathToFileURL(entryPath).href;
84
- const mod = await import(url);
85
- return mod.default || mod.plugin || mod;
86
- }
87
-
88
- class PluginRuntime {
89
- constructor(pluginRoot) {
90
- this.pluginRoot = path.resolve(pluginRoot);
91
- this.packageJson = loadPackageJson(this.pluginRoot);
92
- this.manifest = this.packageJson.omnicrawl || {};
93
- this.handlers = new Map();
94
- this.plugin = null;
95
- this.initialized = false;
96
- this.activeCancellations = new Set();
97
- this.logger = createLogger();
98
- this._hostRequestId = 1;
99
- this._pendingHostRequests = new Map();
100
- }
101
-
102
- /** Host 对 Worker 请求的响应入口(custom.emit 等)。 */
103
- resolveHostResponse(message) {
104
- if (!message || message.id == null) return false;
105
- const pending = this._pendingHostRequests.get(message.id);
106
- if (!pending) return false;
107
- this._pendingHostRequests.delete(message.id);
108
- if (message.error) {
109
- pending.reject(new Error(message.error.message || String(message.error)));
110
- } else {
111
- pending.resolve(message.result || { ok: true });
112
- }
113
- return true;
114
- }
115
-
116
- _requestHost(method, params = {}) {
117
- const id = this._hostRequestId;
118
- this._hostRequestId += 1;
119
- return new Promise((resolve, reject) => {
120
- this._pendingHostRequests.set(id, { resolve, reject });
121
- writeStdout({
122
- jsonrpc: "2.0",
123
- id,
124
- method,
125
- params,
126
- });
127
- // Host 超时由 Python 侧控制;Worker 侧给一个安全兜底。
128
- setTimeout(() => {
129
- if (this._pendingHostRequests.has(id)) {
130
- this._pendingHostRequests.delete(id);
131
- reject(new Error(`Host 请求超时:${method}`));
132
- }
133
- }, 10000);
134
- });
135
- }
136
-
137
- async initialize(params = {}) {
138
- if (this.initialized) {
139
- return this._registrationSnapshot();
140
- }
141
- const entryRel = this.manifest.entry || this.packageJson.main;
142
- if (!entryRel) {
143
- throw new Error("package.json 缺少 omnicrawl.entry / main");
144
- }
145
- const entryPath = path.resolve(this.pluginRoot, entryRel);
146
- const rootResolved = path.resolve(this.pluginRoot);
147
- if (!entryPath.startsWith(rootResolved + path.sep) && entryPath !== rootResolved) {
148
- throw new Error(`entry 逃逸包根目录:${entryRel}`);
149
- }
150
-
151
- const imported = await importPlugin(entryPath);
152
- const definition = typeof imported === "function" ? imported({ definePlugin }) : imported;
153
- if (!definition || typeof definition.activate !== "function") {
154
- throw new Error("插件必须导出 activate(context) 的 PluginDefinition");
155
- }
156
- this.plugin = definition;
157
-
158
- const declared = Array.isArray(this.manifest.hooks) ? this.manifest.hooks : [];
159
- const declaredIds = new Set(declared.map((item) => item.id));
160
-
161
- const context = {
162
- plugin: {
163
- name: this.packageJson.name,
164
- version: this.packageJson.version,
165
- apiVersion: String(this.manifest.apiVersion || "1"),
166
- },
167
- runtime: {
168
- omnicrawlVersion: params.omnicrawlVersion || process.env.OMNICRAWL_VERSION || "0.1.5",
169
- nodeVersion: process.version,
170
- },
171
- hooks: {
172
- on: (registration) => {
173
- if (!registration || typeof registration !== "object") {
174
- throw new Error("hooks.on(registration) 需要对象");
175
- }
176
- const id = String(registration.id || "");
177
- const hook = String(registration.hook || "");
178
- const mode = String(registration.mode || "observe");
179
- const handler = registration.handler;
180
- if (!declaredIds.has(id)) {
181
- throw new Error(`运行期不能注册 manifest 未声明的 Handler:${id}`);
182
- }
183
- if (typeof handler !== "function") {
184
- throw new Error(`Handler ${id} 缺少 handler 函数`);
185
- }
186
- this.handlers.set(id, { id, hook, mode, handler });
187
- return () => {
188
- this.handlers.delete(id);
189
- };
190
- },
191
- emitCustom: async (event, payload = {}, options = {}) => {
192
- if (!this.initialized) {
193
- throw new Error("initialize 完成前不能 emitCustom");
194
- }
195
- const permissions = Array.isArray(this.manifest.permissions)
196
- ? this.manifest.permissions
197
- : [];
198
- if (!permissions.includes("hook:custom-emit")) {
199
- throw new Error("缺少 hook:custom-emit 权限");
200
- }
201
- const version =
202
- options.version != null
203
- ? Number(options.version)
204
- : Number(
205
- (Array.isArray(this.manifest.customEvents)
206
- ? this.manifest.customEvents.find((item) => item.name === event)
207
- : null)?.version || 1
208
- );
209
-
210
- // 同插件订阅必须本地投递:Host 若在 hook.invoke 中再回调本 Worker
211
- // 会与“等待 custom.emit 响应”形成死锁。
212
- let localDelivered = 0;
213
- const localEvent = {
214
- apiVersion: String(this.manifest.apiVersion || "1"),
215
- hook: event,
216
- payload: payload || {},
217
- timestamp: new Date().toISOString(),
218
- };
219
- for (const binding of this.handlers.values()) {
220
- if (binding.hook !== event) continue;
221
- await binding.handler(localEvent);
222
- localDelivered += 1;
223
- }
224
-
225
- const result = await this._requestHost("custom.emit", {
226
- event,
227
- version,
228
- payload: payload || {},
229
- localDelivered,
230
- });
231
- return {
232
- ...(result || { ok: true }),
233
- localDelivered,
234
- delivered: Number(result?.delivered || 0) + localDelivered,
235
- };
236
- },
237
- },
238
- logger: this.logger,
239
- };
240
-
241
- await definition.activate(context);
242
-
243
- // 若插件未在 activate 中显式 on(),但 manifest 声明了 hooks,则保持空绑定;
244
- // Host 会在二次校验时发现实际注册子集。
245
- this.initialized = true;
246
- return this._registrationSnapshot();
247
- }
248
-
249
- _registrationSnapshot() {
250
- const registrations = [];
251
- for (const item of this.handlers.values()) {
252
- registrations.push({
253
- id: item.id,
254
- hook: item.hook,
255
- mode: item.mode,
256
- });
257
- }
258
- // 若 activate 没手动 on,回落为 manifest 声明(开发期便利);
259
- // Host 仍要求它们是 manifest 子集。
260
- if (registrations.length === 0 && Array.isArray(this.manifest.hooks)) {
261
- for (const item of this.manifest.hooks) {
262
- registrations.push({
263
- id: item.id,
264
- hook: item.hook,
265
- mode: item.mode,
266
- });
267
- }
268
- }
269
- return {
270
- plugin: {
271
- name: this.packageJson.name,
272
- version: this.packageJson.version,
273
- apiVersion: String(this.manifest.apiVersion || "1"),
274
- },
275
- handlers: registrations,
276
- capabilities: {
277
- customEmit: Boolean(
278
- Array.isArray(this.manifest.permissions) &&
279
- this.manifest.permissions.includes("hook:custom-emit")
280
- ),
281
- },
282
- };
283
- }
284
-
285
- async invoke(handlerId, event) {
286
- const binding = this.handlers.get(handlerId);
287
- if (!binding) {
288
- // 允许仅声明未绑定:默认 continue,便于 observe 空实现。
289
- return { action: "continue" };
290
- }
291
- const result = await binding.handler(event);
292
- if (result == null) {
293
- return { action: "continue" };
294
- }
295
- if (typeof result !== "object") {
296
- throw new Error(`Handler ${handlerId} 必须返回对象`);
297
- }
298
- return result;
299
- }
300
-
301
- async shutdown() {
302
- if (this.plugin && typeof this.plugin.deactivate === "function") {
303
- await this.plugin.deactivate();
304
- }
305
- this.handlers.clear();
306
- this.initialized = false;
307
- }
308
- }
309
-
310
- async function main() {
311
- const args = parseArgs(process.argv.slice(2));
312
- if (!args.pluginRoot) {
313
- writeStderrLine("error", "missing --plugin-root");
314
- process.exit(2);
315
- }
316
- const runtime = new PluginRuntime(args.pluginRoot);
317
- const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
318
-
319
- const sendResult = (id, result) => {
320
- writeStdout({ jsonrpc: "2.0", id, result });
321
- };
322
- const sendError = (id, code, message) => {
323
- writeStdout({ jsonrpc: "2.0", id, error: { code, message } });
324
- };
325
-
326
- // 必须用事件驱动读取 stdin:handler 内 await custom.emit 时 Host 响应
327
- // 也要从同一 stdin 进来。若 for-await 串行阻塞,会形成死锁超时。
328
- let shuttingDown = false;
329
- const inflight = new Set();
330
-
331
- const handleMessage = async (message) => {
332
- // Host 对 Worker 请求的响应:无 method,带 id+result/error。
333
- if (
334
- message.id != null &&
335
- (Object.prototype.hasOwnProperty.call(message, "result") ||
336
- Object.prototype.hasOwnProperty.call(message, "error")) &&
337
- !message.method
338
- ) {
339
- runtime.resolveHostResponse(message);
340
- return;
341
- }
342
-
343
- const { id, method, params } = message;
344
- try {
345
- if (method === "initialize") {
346
- const result = await runtime.initialize(params || {});
347
- if (id !== undefined) sendResult(id, result);
348
- } else if (method === "hook.invoke") {
349
- const handlerId = params?.handlerId;
350
- const event = params?.event || {};
351
- const result = await runtime.invoke(handlerId, event);
352
- if (id !== undefined) sendResult(id, result);
353
- } else if (method === "hook.cancel") {
354
- const requestId = params?.requestId;
355
- if (requestId != null) runtime.activeCancellations.add(requestId);
356
- if (id !== undefined) sendResult(id, { ok: true });
357
- } else if (method === "ping") {
358
- if (id !== undefined) sendResult(id, { ok: true, ts: Date.now() });
359
- } else if (method === "shutdown") {
360
- shuttingDown = true;
361
- await runtime.shutdown();
362
- if (id !== undefined) sendResult(id, { ok: true });
363
- process.exit(0);
364
- } else if (method) {
365
- if (id !== undefined) sendError(id, -32601, `Method not found: ${method}`);
366
- }
367
- } catch (error) {
368
- const text = error instanceof Error ? error.message : String(error);
369
- writeStderrLine("error", text);
370
- if (id !== undefined) sendError(id, -32000, text);
371
- }
372
- };
373
-
374
- rl.on("line", (line) => {
375
- if (shuttingDown) return;
376
- const trimmed = String(line || "").trim();
377
- if (!trimmed) return;
378
- let message;
379
- try {
380
- message = JSON.parse(trimmed);
381
- } catch (error) {
382
- writeStderrLine("error", "invalid json from host", { error: String(error) });
383
- return;
384
- }
385
- if (!message || typeof message !== "object") return;
386
- const task = handleMessage(message);
387
- inflight.add(task);
388
- task.finally(() => inflight.delete(task));
389
- });
390
-
391
- await new Promise((resolve) => {
392
- rl.on("close", resolve);
393
- });
394
- await Promise.allSettled([...inflight]);
395
- }
396
-
397
- main().catch((error) => {
398
- writeStderrLine("error", error instanceof Error ? error.message : String(error));
399
- process.exit(1);
400
- });
401
-
402
- export { definePlugin };
@@ -1,402 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * OmniCrawl 单插件 Node runner。
4
- *
5
- * Host 固定调用:
6
- * node node_runner.mjs --plugin-root <canonical-path>
7
- *
8
- * 协议:stdin/stdout NDJSON JSON-RPC 2.0。
9
- * 插件日志经 SDK logger 走 stderr 结构化行;普通 console 也被重定向到 stderr。
10
- */
11
-
12
- import { createRequire } from "node:module";
13
- import path from "node:path";
14
- import process from "node:process";
15
- import { pathToFileURL } from "node:url";
16
- import readline from "node:readline";
17
- import fs from "node:fs";
18
-
19
- const require = createRequire(import.meta.url);
20
-
21
- function parseArgs(argv) {
22
- const args = { pluginRoot: "" };
23
- for (let i = 0; i < argv.length; i += 1) {
24
- const item = argv[i];
25
- if (item === "--plugin-root") {
26
- args.pluginRoot = argv[i + 1] || "";
27
- i += 1;
28
- }
29
- }
30
- return args;
31
- }
32
-
33
- function writeStdout(message) {
34
- process.stdout.write(`${JSON.stringify(message)}\n`);
35
- }
36
-
37
- function writeStderrLine(level, message, fields = {}) {
38
- const payload = {
39
- level,
40
- message: String(message),
41
- fields,
42
- ts: new Date().toISOString(),
43
- };
44
- process.stderr.write(`${JSON.stringify(payload)}\n`);
45
- }
46
-
47
- // 强制把 console 导向 stderr,避免污染协议 stdout。
48
- const originalConsole = { ...console };
49
- for (const level of ["log", "info", "warn", "error", "debug"]) {
50
- console[level] = (...parts) => {
51
- writeStderrLine(level === "log" ? "info" : level, parts.map(String).join(" "));
52
- };
53
- }
54
-
55
- function createLogger() {
56
- return {
57
- debug(message, fields) {
58
- writeStderrLine("debug", message, fields || {});
59
- },
60
- info(message, fields) {
61
- writeStderrLine("info", message, fields || {});
62
- },
63
- warn(message, fields) {
64
- writeStderrLine("warn", message, fields || {});
65
- },
66
- error(message, fields) {
67
- writeStderrLine("error", message, fields || {});
68
- },
69
- };
70
- }
71
-
72
- function definePlugin(plugin) {
73
- return plugin;
74
- }
75
-
76
- function loadPackageJson(pluginRoot) {
77
- const packagePath = path.join(pluginRoot, "package.json");
78
- const raw = fs.readFileSync(packagePath, "utf8");
79
- return JSON.parse(raw);
80
- }
81
-
82
- async function importPlugin(entryPath) {
83
- const url = pathToFileURL(entryPath).href;
84
- const mod = await import(url);
85
- return mod.default || mod.plugin || mod;
86
- }
87
-
88
- class PluginRuntime {
89
- constructor(pluginRoot) {
90
- this.pluginRoot = path.resolve(pluginRoot);
91
- this.packageJson = loadPackageJson(this.pluginRoot);
92
- this.manifest = this.packageJson.omnicrawl || {};
93
- this.handlers = new Map();
94
- this.plugin = null;
95
- this.initialized = false;
96
- this.activeCancellations = new Set();
97
- this.logger = createLogger();
98
- this._hostRequestId = 1;
99
- this._pendingHostRequests = new Map();
100
- }
101
-
102
- /** Host 对 Worker 请求的响应入口(custom.emit 等)。 */
103
- resolveHostResponse(message) {
104
- if (!message || message.id == null) return false;
105
- const pending = this._pendingHostRequests.get(message.id);
106
- if (!pending) return false;
107
- this._pendingHostRequests.delete(message.id);
108
- if (message.error) {
109
- pending.reject(new Error(message.error.message || String(message.error)));
110
- } else {
111
- pending.resolve(message.result || { ok: true });
112
- }
113
- return true;
114
- }
115
-
116
- _requestHost(method, params = {}) {
117
- const id = this._hostRequestId;
118
- this._hostRequestId += 1;
119
- return new Promise((resolve, reject) => {
120
- this._pendingHostRequests.set(id, { resolve, reject });
121
- writeStdout({
122
- jsonrpc: "2.0",
123
- id,
124
- method,
125
- params,
126
- });
127
- // Host 超时由 Python 侧控制;Worker 侧给一个安全兜底。
128
- setTimeout(() => {
129
- if (this._pendingHostRequests.has(id)) {
130
- this._pendingHostRequests.delete(id);
131
- reject(new Error(`Host 请求超时:${method}`));
132
- }
133
- }, 10000);
134
- });
135
- }
136
-
137
- async initialize(params = {}) {
138
- if (this.initialized) {
139
- return this._registrationSnapshot();
140
- }
141
- const entryRel = this.manifest.entry || this.packageJson.main;
142
- if (!entryRel) {
143
- throw new Error("package.json 缺少 omnicrawl.entry / main");
144
- }
145
- const entryPath = path.resolve(this.pluginRoot, entryRel);
146
- const rootResolved = path.resolve(this.pluginRoot);
147
- if (!entryPath.startsWith(rootResolved + path.sep) && entryPath !== rootResolved) {
148
- throw new Error(`entry 逃逸包根目录:${entryRel}`);
149
- }
150
-
151
- const imported = await importPlugin(entryPath);
152
- const definition = typeof imported === "function" ? imported({ definePlugin }) : imported;
153
- if (!definition || typeof definition.activate !== "function") {
154
- throw new Error("插件必须导出 activate(context) 的 PluginDefinition");
155
- }
156
- this.plugin = definition;
157
-
158
- const declared = Array.isArray(this.manifest.hooks) ? this.manifest.hooks : [];
159
- const declaredIds = new Set(declared.map((item) => item.id));
160
-
161
- const context = {
162
- plugin: {
163
- name: this.packageJson.name,
164
- version: this.packageJson.version,
165
- apiVersion: String(this.manifest.apiVersion || "1"),
166
- },
167
- runtime: {
168
- omnicrawlVersion: params.omnicrawlVersion || process.env.OMNICRAWL_VERSION || "0.1.5",
169
- nodeVersion: process.version,
170
- },
171
- hooks: {
172
- on: (registration) => {
173
- if (!registration || typeof registration !== "object") {
174
- throw new Error("hooks.on(registration) 需要对象");
175
- }
176
- const id = String(registration.id || "");
177
- const hook = String(registration.hook || "");
178
- const mode = String(registration.mode || "observe");
179
- const handler = registration.handler;
180
- if (!declaredIds.has(id)) {
181
- throw new Error(`运行期不能注册 manifest 未声明的 Handler:${id}`);
182
- }
183
- if (typeof handler !== "function") {
184
- throw new Error(`Handler ${id} 缺少 handler 函数`);
185
- }
186
- this.handlers.set(id, { id, hook, mode, handler });
187
- return () => {
188
- this.handlers.delete(id);
189
- };
190
- },
191
- emitCustom: async (event, payload = {}, options = {}) => {
192
- if (!this.initialized) {
193
- throw new Error("initialize 完成前不能 emitCustom");
194
- }
195
- const permissions = Array.isArray(this.manifest.permissions)
196
- ? this.manifest.permissions
197
- : [];
198
- if (!permissions.includes("hook:custom-emit")) {
199
- throw new Error("缺少 hook:custom-emit 权限");
200
- }
201
- const version =
202
- options.version != null
203
- ? Number(options.version)
204
- : Number(
205
- (Array.isArray(this.manifest.customEvents)
206
- ? this.manifest.customEvents.find((item) => item.name === event)
207
- : null)?.version || 1
208
- );
209
-
210
- // 同插件订阅必须本地投递:Host 若在 hook.invoke 中再回调本 Worker
211
- // 会与“等待 custom.emit 响应”形成死锁。
212
- let localDelivered = 0;
213
- const localEvent = {
214
- apiVersion: String(this.manifest.apiVersion || "1"),
215
- hook: event,
216
- payload: payload || {},
217
- timestamp: new Date().toISOString(),
218
- };
219
- for (const binding of this.handlers.values()) {
220
- if (binding.hook !== event) continue;
221
- await binding.handler(localEvent);
222
- localDelivered += 1;
223
- }
224
-
225
- const result = await this._requestHost("custom.emit", {
226
- event,
227
- version,
228
- payload: payload || {},
229
- localDelivered,
230
- });
231
- return {
232
- ...(result || { ok: true }),
233
- localDelivered,
234
- delivered: Number(result?.delivered || 0) + localDelivered,
235
- };
236
- },
237
- },
238
- logger: this.logger,
239
- };
240
-
241
- await definition.activate(context);
242
-
243
- // 若插件未在 activate 中显式 on(),但 manifest 声明了 hooks,则保持空绑定;
244
- // Host 会在二次校验时发现实际注册子集。
245
- this.initialized = true;
246
- return this._registrationSnapshot();
247
- }
248
-
249
- _registrationSnapshot() {
250
- const registrations = [];
251
- for (const item of this.handlers.values()) {
252
- registrations.push({
253
- id: item.id,
254
- hook: item.hook,
255
- mode: item.mode,
256
- });
257
- }
258
- // 若 activate 没手动 on,回落为 manifest 声明(开发期便利);
259
- // Host 仍要求它们是 manifest 子集。
260
- if (registrations.length === 0 && Array.isArray(this.manifest.hooks)) {
261
- for (const item of this.manifest.hooks) {
262
- registrations.push({
263
- id: item.id,
264
- hook: item.hook,
265
- mode: item.mode,
266
- });
267
- }
268
- }
269
- return {
270
- plugin: {
271
- name: this.packageJson.name,
272
- version: this.packageJson.version,
273
- apiVersion: String(this.manifest.apiVersion || "1"),
274
- },
275
- handlers: registrations,
276
- capabilities: {
277
- customEmit: Boolean(
278
- Array.isArray(this.manifest.permissions) &&
279
- this.manifest.permissions.includes("hook:custom-emit")
280
- ),
281
- },
282
- };
283
- }
284
-
285
- async invoke(handlerId, event) {
286
- const binding = this.handlers.get(handlerId);
287
- if (!binding) {
288
- // 允许仅声明未绑定:默认 continue,便于 observe 空实现。
289
- return { action: "continue" };
290
- }
291
- const result = await binding.handler(event);
292
- if (result == null) {
293
- return { action: "continue" };
294
- }
295
- if (typeof result !== "object") {
296
- throw new Error(`Handler ${handlerId} 必须返回对象`);
297
- }
298
- return result;
299
- }
300
-
301
- async shutdown() {
302
- if (this.plugin && typeof this.plugin.deactivate === "function") {
303
- await this.plugin.deactivate();
304
- }
305
- this.handlers.clear();
306
- this.initialized = false;
307
- }
308
- }
309
-
310
- async function main() {
311
- const args = parseArgs(process.argv.slice(2));
312
- if (!args.pluginRoot) {
313
- writeStderrLine("error", "missing --plugin-root");
314
- process.exit(2);
315
- }
316
- const runtime = new PluginRuntime(args.pluginRoot);
317
- const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
318
-
319
- const sendResult = (id, result) => {
320
- writeStdout({ jsonrpc: "2.0", id, result });
321
- };
322
- const sendError = (id, code, message) => {
323
- writeStdout({ jsonrpc: "2.0", id, error: { code, message } });
324
- };
325
-
326
- // 必须用事件驱动读取 stdin:handler 内 await custom.emit 时 Host 响应
327
- // 也要从同一 stdin 进来。若 for-await 串行阻塞,会形成死锁超时。
328
- let shuttingDown = false;
329
- const inflight = new Set();
330
-
331
- const handleMessage = async (message) => {
332
- // Host 对 Worker 请求的响应:无 method,带 id+result/error。
333
- if (
334
- message.id != null &&
335
- (Object.prototype.hasOwnProperty.call(message, "result") ||
336
- Object.prototype.hasOwnProperty.call(message, "error")) &&
337
- !message.method
338
- ) {
339
- runtime.resolveHostResponse(message);
340
- return;
341
- }
342
-
343
- const { id, method, params } = message;
344
- try {
345
- if (method === "initialize") {
346
- const result = await runtime.initialize(params || {});
347
- if (id !== undefined) sendResult(id, result);
348
- } else if (method === "hook.invoke") {
349
- const handlerId = params?.handlerId;
350
- const event = params?.event || {};
351
- const result = await runtime.invoke(handlerId, event);
352
- if (id !== undefined) sendResult(id, result);
353
- } else if (method === "hook.cancel") {
354
- const requestId = params?.requestId;
355
- if (requestId != null) runtime.activeCancellations.add(requestId);
356
- if (id !== undefined) sendResult(id, { ok: true });
357
- } else if (method === "ping") {
358
- if (id !== undefined) sendResult(id, { ok: true, ts: Date.now() });
359
- } else if (method === "shutdown") {
360
- shuttingDown = true;
361
- await runtime.shutdown();
362
- if (id !== undefined) sendResult(id, { ok: true });
363
- process.exit(0);
364
- } else if (method) {
365
- if (id !== undefined) sendError(id, -32601, `Method not found: ${method}`);
366
- }
367
- } catch (error) {
368
- const text = error instanceof Error ? error.message : String(error);
369
- writeStderrLine("error", text);
370
- if (id !== undefined) sendError(id, -32000, text);
371
- }
372
- };
373
-
374
- rl.on("line", (line) => {
375
- if (shuttingDown) return;
376
- const trimmed = String(line || "").trim();
377
- if (!trimmed) return;
378
- let message;
379
- try {
380
- message = JSON.parse(trimmed);
381
- } catch (error) {
382
- writeStderrLine("error", "invalid json from host", { error: String(error) });
383
- return;
384
- }
385
- if (!message || typeof message !== "object") return;
386
- const task = handleMessage(message);
387
- inflight.add(task);
388
- task.finally(() => inflight.delete(task));
389
- });
390
-
391
- await new Promise((resolve) => {
392
- rl.on("close", resolve);
393
- });
394
- await Promise.allSettled([...inflight]);
395
- }
396
-
397
- main().catch((error) => {
398
- writeStderrLine("error", error instanceof Error ? error.message : String(error));
399
- process.exit(1);
400
- });
401
-
402
- export { definePlugin };