← 返回列表
✓ 可直接安装
一个确定性的、由人编写的地平线——一份简短的未决缺口列表加上一份
自动检查通过:npm 包已发布且 engines 声明满足基线(声明 Node >=22.18);该结论来自程序自动检查,未经人工实机验证。 · 最近上游提交 2026/9/16 · 已提供中文文档
Deep Horizon 是一个用于注入长期目标的 AI 插件集
综合分
29.4
GitHub 分
29.4
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add deep-horizonnpm 包 deep-horizon 已校验归属本仓库,走 npm 安装最省事
信任档位:已验证本站已于 1 天前真实安装成功
- 是什么
- dsh 原生插件 · chat
- 装得上吗
- 本站已真实安装成功(非静态推断)
- 安全吗
- 本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
- 还在维护吗
- 活跃:最近一次提交在 10 天前
档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →
🟢实装验证通过· 2026/9/25
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/21(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查✓ 自动检查通过
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包deep-horizon @ 0.4.0
✓Node 引擎要求 >=22.18 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/22 19:19:26
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
由 DeepSeek 最新模型翻译生成deep-horizon
一个确定性的、由人编写的地平线——一份简短的未决缺口列表加上一份
会话日志——在 AI 智能体框架之间共享,并在每个新会话开始时注入。
地平线是人想要但尚未拥有的东西:最多 5 个未决缺口,每个一行,仅在人
同意后才由智能体编写。这里没有任何东西是任务列表;缺口会开放数周,而这
是正常情况。每个框架读写同一个存储,因此目标在会话之间和工具之间得以
延续。
安装
先装 CLI,再装适配器。 在任何适配器被配置之前,二进制文件必须位于
PATH 上;当二进制文件缺失时,钩子会静默失败(它们不注入任何内容)。
npm install -g deep-horizon
注册表 tarball 按打包原样附带 dist/,因此安装时不会运行构建步骤,也不会
运行生命周期脚本——npm >= 12 的 install-scripts 保护在这条路径上无关紧要。
安装后,接入各框架——install 会自行写入钩子配置(解析、合并、校验、带
时间戳的备份;它会大声拒绝,而不是盲目写入无法识别的结构),doctor 会
验证结果:
horizon install --harness zcode --harness hermes
horizon doctor
改为从代码检出安装(在适配器上折腾时):先构建再安装。
在 npm >= 12 上,install-scripts 保护会阻止此包的 prepare(构建)在路径
安装时运行,而 npm 自身警告所建议的 --allow-scripts 补救措施在路径安装
时仍然会被阻止——未构建的克隆安装出的二进制文件会因 dist/cli.js 的
ERR_MODULE_NOT_FOUND 而崩溃。(npm pack 不受影响;prepare 在那里仍会
运行。)
git clone https://github.com/andrepontesmelo/deep-horizon
cd deep-horizon
npm install && npm run build
npm install -g .
文件夹安装是符号链接的,因此全局二进制文件运行的是此检出目录的
dist/——在克隆中构建,安装就是完整的。
状态:此包附带 CLI 核心(horizon)、horizon-inject 组合器,以及下方
每个框架的适配器(Claude Code 没有 npm 制品——它的 settings 块就是适配器)。
在 CLI 位于 PATH 上之后,horizon install --harness zcode|hermes 会为你接入
适配器配置,horizon doctor 会验证接入——参见下方的接入。
CLI
在项目内运行 horizon;存储位于 .horizon/(向上查找,就像 git 查找
.git 一样)。horizon init 会创建它。
usage: horizon [--cwd ] [--json] [--harness ] [--session ] [--origin ] [...]
commands:
show print open gaps (id + two spaces + text)
about print the about line
about "" set or replace the about line (what this project is)
about --clear unset the about line
add "" append a gap under a caller-chosen slug id; prints the id (--detail "" attaches details)
close 移除一个缺口;释放一个槽位
amend "" 就地重写缺口的文本
detail 打印缺口的详细信息
detail "" 设置或重写缺口的详细信息(最多 2048 个码点)
detail --clear 移除缺口的详细信息
log [--limit N] 打印会话记录,最新的在前
session-end --harness --session [--summary ""] [--store ] — 每个 (harness, session) 一次;重复调用是静默无操作
init 在 --cwd 中创建 .horizon/
doctor [--harness ] 针对每个 harness 的只读接线检查(zcode、hermes;默认两者)— 每项检查一行 PASS/FAIL 并附带修复提示;全部通过时退出码为 0,任一 FAIL 时为 1
install --harness 接线某个 harness 的全局配置(zcode、hermes;可重复该标志或用逗号分隔);解析 → 合并 → 验证 → 备份 → 原子写入,幂等
缺口的标题是一行;它还可以携带可选的详细信息——扩展上下文
(是什么、为什么、来自哪里),这些内容不能挤占那一行。详细信息
可以多行,最多 2048 个 Unicode 码点,在创建时用
add --detail "" 设置,或之后用 horizon detail 设置。它们
永远不会被注入:注入的 horizon 只携带那一行,外加一个指针,
表明 horizon detail 可按需检索其余内容。
只有人类才能关闭缺口。智能体提出建议——horizon add / horizon
close 仅在人类同意后才运行。缺口 id 是选定的,而非铸造的:
horizon add plant-photo-lookup "A person can hand a photo to the app and get
the plant named."——一个由 3–40 个小写字母、数字和连字符组成的 slug,
以字母开头。id 永远不会被重用,即使其缺口关闭后也是如此,因此一个
名称在项目的整个生命周期内始终意味着同一个缺口。about 行是一行
由人类撰写的文字,说明这个项目是什么(缺口说明工作正走向
何处);设置后,它会被注入到 horizon 块之前。存储文件:
.horizon/gaps.json(打开的缺口、已关闭缺口的退役 id,
以及 about 行)、.horizon/sessions.jsonl 和 .horizon/closes.jsonl
(仅追加的历史记录)。
存储与 git
gaps.json 是共享的真相:提交它。仅追加的日志
(sessions.jsonl、closes.jsonl、hooks.log)在设计上属于
机器本地——被每个存储都携带的 .gitignore 忽略,永不
合并,因为并发的仅追加日志会终其一生陷入
合并冲突,且没有跨机器的价值。这个取舍,只说一次:
什么处于打开或关闭状态随仓库一起传播;它何时发生
以及在哪个会话中发生留在它发生的机器上。
Git 工作树是必然结果,而非问题:一个已提交的 gaps.json
不会分叉存储,而是分支它——每个工作树检出
像任何其他文件一样随其分支而行,将分支合并回家
会随之调和缺口。工作树的 sessions.jsonl 是该
检出的日记,留在那里。horizon init 写入嵌套的
实现所有这些的 .gitignore;本仓库自己的根 .gitignore 所使用的模式(忽略 .horizon/,重新包含 gaps.json、.gitignore、.gitattributes)是应当照抄的规范模式。
适配器通信接口
每个 harness 的粘合代码都是相同的两次调用,而且两者都不会自行发现存储。horizon-inject --harness --cwd 打印要注入的文本——输出为空表示静默。使用 --json 时,答案是完整的:一行 JSON,{"text": , "store": }——组合后的文本加上组合器已经解析出的存储目录(未找到存储时为 null)。关闭钩子会把该存储传回,因此拆除时永远不会重新发现它:horizon session-end --harness --session --store (没有存储时该命令仍然可用,会从 --cwd 发现;不存在的路径是静默无操作)。
接线:horizon install、horizon doctor
两个适配器依托全局配置界面——ZCode 的 ~/.zcode/cli/config.json 和 Hermes 的 ~/.hermes——horizon install 会应用它们,这样就不必有人手动编辑 JSON:
horizon install --harness zcode # repeat the flag or comma-separate: --harness zcode,hermes
horizon install --harness hermes
ZCode:配置会被解析、合并、校验、备份(旁边生成 config.json.bak-pre-horizon-),并以原子方式写入——绝不是盲目写入。无法解析的配置,或 install 无法识别的 hooks 结构,都会是响亮的拒绝(退出码 2,手动修复):无法解析的配置会在 ZCode 中禁用整个文件,因此覆盖它是唯一不可恢复的操作。合并会写入下文 ZCode 部分所记录的代码块(SessionStart 匹配器 startup|resume,PreToolUse pre-execute),保留每一个外来键,只替换 deep-horizon 自己的条目(通过命令中命名 deep-horizon 来匹配),并且不触碰 Stop。仅限全局配置:项目钩子受信任门控,并且当两个作用域都声明它们时,一旦受信任就会双重触发——每台机器恰好一个界面。Hermes:插件文件会被复制到 ~/.hermes/plugins/deep-horizon/(字节完全相同的文件会被跳过,过期的 __pycache__ 会被移除),并且 ~/.hermes/config.yaml 会在 plugins.enabled 中增加 deep-horizon——追加在最后,已备份,其他内容不重新排序;install 无法有把握读取的 yaml 会被拒绝,而不是猜测。在已经接线的情况下重新运行 install 不会改变任何东西(并且当不会写入任何内容时不会进行备份);升级方式是 npm i -g deep-horizon@latest && horizon install --harness zcode --harness hermes && horizon doctor——合并就是迁移,不存在配置迁移机制。
horizon doctor 是只读的孪生命令:对每个 harness,每项检查输出一行 PASS/FAIL 并附带修复提示,一切接线正常时退出码为 0,有未接线时退出码为 1。它会检查 PATH 上的可执行文件、配置是否存在及能否解析、两个 ZCode 钩子(缺少 PreToolUse 就是 F3 类“已发布但未接线”
状态——检查会指出它并给出修复方案),而对于 Hermes,则是插件文件、
plugins.enabled 条目以及 shim。缺失和损坏的配置都是 FAIL 行,绝不会是崩溃。
horizon doctor # 两个 harness;--harness zcode 可缩小范围
各 harness 的设置
1. DeepSeek Harness (DSH) —— 参考实现
从已构建的检出安装,先安装 CLI:
git clone https://github.com/andrepontesmelo/deep-horizon
cd deep-horizon
npm install && npm run build # 先在克隆目录中构建——参见 Install
npm install -g . # CLI,来自此克隆
npm pack # 插件 tgz,来自已构建的检出
dsh --profile --from-default-profile sdk-minimal --dump-config
dsh plugin --profile add file:/abs/path/deep-horizon-.tgz
升级:提升版本号,执行 npm pack,然后重新运行 dsh plugin add——
或者移除插件,再重新添加。重新添加未变更的版本会打印
“Already up to date”,并静默保留旧内容,而 --dump-config
无法发现过时的内容。
npm pack deep-horizon(按名称)打包的是注册表副本,而不是这份代码。
plugin add 会警告 deep-horizon “declares no dsh.bundle — installed as a
plain dependency, not a profile layer”。这是预期行为;它隐含的手动步骤
就是下面的挂载行。dsh plugin add npm:deep-horizon 是否可用
未经探测——tgz 路径是文档中记载的方式。
在 profile 的 cordis.patch.yml 中挂载:
- insert:
- id: deep-horizon
name: deep-horizon/dsh
然后用 dsh --profile --dump-config 验证接线。
在会话开始时(agent/session-start),适配器会布置一个
horizon-inject resolve——在事件触发的那一刻,spawn 尚未完成——
并在会话的第一个模型步骤交付该块:被等待的 agent/pre-step 瀑布流
会保持步骤 1 直到答案到达,因此第一个请求已经携带该块,且位于启动提示之前
(session-start 本身是即发即忘的;在那里触发的注入,而不是为第一步布置的注入,
会落入第二步的请求)。失败或超时的 resolve
(15 秒 bin 限制)会让步骤 1 保持干净,且该未命中可重试。每个 agent 只播种一次——
仅限全新、顶层的启动
(source === "startup";恢复或压缩的会话会重放其原始
注入,子 agent 永远不会获得注入)。
该块是 composer 的三种变体之一:在没有 store 或 store 为空的项目上的
bootstrap 提示,在设置了 about 行但没有设置 gaps 时的
warm 提示,在 gaps 打开时的 horizon 块(设置后 about 行会作为其前缀)。
从主目录发起的无 store 启动保持静默——$HOME 不是项目。
在仓库 A 中启动、并在会话中途触及仓库 B 的会话,由
第二条注入路径覆盖,即 param trigger(tools/pre-execute):每次工具
调用的参数都会被检查以寻找目标目录——一个 workdir 字段,
file_path/path 参数的目录,以及 command 字符串中的绝对路径(这涵盖了 git -C 的目标)。当一个被触及的目录解析到一个带有 horizon 存储的仓库时,该仓库的 horizon 会被排入下一步——每个会话每个仓库一次,启动仓库自身的 horizon 永远不会被重新触发,子代理除外,并且对于无存储的目标永远不会触发(不会为其创建或提供存储;一个已存在但为空的存储仍会像启动路径上那样组合引导提示)。与适配器中的其他一切一样,它会失败开放:格式错误的参数、生成失败或错误都不会排入任何内容,也永远不会阻塞工具调用。
会话结束有两条路径。会话中途,在顶层会话的第一次回合停止时,适配器会在用户批准的情况下引导代理运行一次:
horizon session-end --harness dsh --session [--summary ""]
省略 --summary 会记录 summary: null——摘要永远不会被捏造。该引导是唯一可以携带摘要的路径,因为它是唯一可以请求摘要的路径。
而当会话从未到达回合停止时:DSH 启动器自身注册 SIGINT/SIGTERM,并在 5 秒强制退出预算内等待整个插件树的处置(agent/disposed,即循环级事件,保持不被等待——但插件级 dispose 是另一种机制,它会被等待)。在第一次 Ctrl-C 或 SIGTERM 时,适配器的 disposer 会在该预算内运行,并同步地为会话所服务的每个存储写入一条 summary-null 记录——启动仓库加上参数触发器所认领的每个仓库(会话留下的触及是值得记录的足迹,即使注入本身未能送达):
horizon session-end --harness dsh --session --store
第二次 Ctrl-C 会越过预算强制退出并截断写入;SIGHUP——关闭的终端窗口——完全没有处理器,会丢失记录。实时补丁重载也会卸载插件,但不写入任何内容:它不是会话结束,而一条过早的 summary-null 记录会消耗 session-end 幂等槽位(第一条记录胜出),而稍后经用户批准的引导摘要需要该槽位。
2. Claude Code
npm install -g deep-horizon # the CLI — see Install
.claude/settings.json(项目本地,检入仓库):
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{ "type": "command", "command": "horizon-inject --harness claude-code" }
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "horizon session-end --harness claude-code --session \"$(jq -r .session_id)\""
}
]
}
]
}
}
仅 startup:恢复/压缩/分叉的会话会从其转录中重放原始注入,因此重新注入会使其重复。已知的刻意缺口:/clear 会清除上下文但不会重新注入(其来源
(仅启动钩子不匹配)——地平线在下次启动时返回。
子代理在结构上被排除:SessionStart 钩子不会为它们触发
(它们会收到 SubagentStart)。
SessionEnd 是唯一被证明会在 SIGINT/SIGTERM 上触发的关闭钩子;其
stdin JSON 携带会话 id(jq -r .session_id 可读取它),并且
记录携带 summary: null —— 关闭钩子无法引出模型文本。
3. pi
npm install -g deep-horizon # the CLI — see Install
~/.pi/agent/settings.json:
{ "packages": ["npm:deep-horizon"] }
扩展入口点是该包的 exports["./pi"] 模块:
session_start 会调用 horizon-inject 并暂存其输出;第一个
before_agent_start 提示会将其作为持久消息返回。只有
startup 原因会暂存 —— new、resume、fork 和 reload 会到达
一个正在运行的进程内部,或重放现有的地平线。git: 包
说明符已被证明可用;npm:deep-horizon 目前无法解析(没有
注册表发布),因此请将该说明符指向仓库。
子代理退出: pi 没有用于区分子代理会话的判别器,因此当
HORIZON_SUBAGENT 被设置为真值(1、true、yes)时,适配器会跳过注入。
子代理扩展应在子进程环境中导出 HORIZON_SUBAGENT=1。
故障开放:缺少该变量时,注入会发生 —— 收到地平线的子代理是噪声,而非危害。
关闭:session_shutdown 运行 horizon session-end --harness pi --session
,并传入 --store 以及启动注入所解析到的目录 ——
既能在投递后存活(文本暂存在第一个提示时被消耗),
也能在任何进程 chdir 后存活。若没有暂存的存储,该命令会保持其
cwd 发现形式。无论哪种方式都没有 --summary,因此记录会携带
summary: null。
关闭有一个漏洞,直白地说:SIGTERM 会运行 session_shutdown,
记录会落地,但关闭终端会发送 SIGHUP,而 pi 的 SIGHUP
路径是刻意的紧急退出 —— 它会完全跳过扩展清理,
因为向已死终端写入的清理可能会重新触发它正在清理的
那个 EIO。因此,由终端关闭结束的会话不会写入
记录。(交互式 TUI 内的 Ctrl-C 是原始模式按键,不是
信号,无论如何都不会到达 pi 的进程处理器。)
4. opencode — 降级
npm install -g deep-horizon # the CLI — see Install
opencode.json(项目):
{ "plugin": ["deep-horizon"] }
opencode 以降级状态发布,README 也直白地说明了这一点:地平线块会
作为第一条用户消息的额外文本部分添加,每个会话一次,基于
新鲜度启发式(最近的 session.time.created 加上空的持久化
历史 —— 新会话与恢复会话是推断出来的,而非已知的);子代理会话
通过 session.parentID 排除;并且 opencode 会话不写入会话
记录* —— 其插件钩子中不存在关闭钩子,而在信号上,
二进制文件自身的处理器只是裸的 process.exit() 调用,因此退出时不会有任何优雅处理运行。opencode 会话对日志不可见。它的间隙仍然像其他所有 harness 一样读写。
"plugin": ["deep-horizon"] 会解析一个注册表包,而目前还没有这样的包——如今的做法是使用位于 .opencode/plugin/deep-horizon.ts 的本地插件(经实际验证的形态):
import { apply } from "deep-horizon/opencode";
export default async function deepHorizon(ctx) {
return await apply(ctx);
}
5. Hermes
从 horizon-line 升级:先禁用并移除旧插件,否则两者都会注入——hermes plugins disable horizon-line,然后删除 ~/.hermes/plugins/horizon-line,再安装 deep-horizon。
git clone https://github.com/andrepontesmelo/deep-horizon
npm install -g deep-horizon # the CLI — see Install
rsync -a --exclude __pycache__ deep-horizon/adapters/hermes/ ~/.hermes/plugins/deep-horizon/
rm -rf ~/.hermes/plugins/deep-horizon/__pycache__ # upgrades: --exclude keeps the OLD bytecode
no rsync? coreutils only:
cp -r deep-horizon/adapters/hermes ~/.hermes/plugins/deep-horizon && \
rm -rf ~/.hermes/plugins/deep-horizon/__pycache__
hermes plugins enable deep-horizon
systemctl --user restart hermes-gateway # the gateway loads plugins at start
(rm -rf 在升级时很重要:--exclude 会阻止 rsync 把克隆中的 __pycache__ 复制进去,但它也会阻止 rsync 删除目标位置中过时的字节码——而裸 cp -r 也会把它带过去。)
从检出目录运行 npm run sync:hermes(或 bash scripts/sync-hermes-adapter.sh)会执行上面的复制,外加一次一致性差异比对——用一个可重复的步骤取代仓库与插件目录之间的静默漂移。HERMES_PLUGIN_DIR 可覆盖目标位置。
cwd 约定:网关会话从 hermes 配置中的 terminal.cwd 获取其工作目录。占位值(.)会解析为主目录——请将其设置为真实项目根目录,或导出 TERMINAL_CWD。插件自身从不查找存储:它询问 horizon-inject --json,而当 bin 在启动目录中找到存储时,无存储的会话 cwd 会遵从启动目录。
一次性(-z)会话也会最终化:CLI 的一次性退出路径会运行与网关相同的 on_session_finalize,因此注入了的 -z 会话也会写入其记录。
Python 插件在三个点将 horizon 接入 Hermes。所有派生都通过 horizon / horizon-inject bin 进行(插件自身从不组合文本),并且每个钩子都故障开放——缺失的 bin、超时或错误都不会注入任何内容,也绝不会阻塞会话。
- 会话开始(冻结区段): 一个持久的系统提示区段会为会话的工作目录派生 horizon-inject --harness hermes。核心会渲染它一次,将其冻结进提示中,并原样持久化,因此恢复会话绝不会重复它。子代理会话不渲染任何内容。
- 每一轮(参数触发): 在每个用户轮次之后,插件仅扫描
新的助手工具调用参数——terminal(命令 + 工作目录)、
read_file / write_file / patch / search_files(路径)、
execute_code(代码)——用于 git 根目录(/home/andre/git/
或 ~/git/)下的触碰。当被触碰的仓库有 .horizon/ 存储时,其 horizon 会在该轮次中按(会话,仓库)注入一次。工具结果和用户
消息永远不会被扫描(询问“列出我工作区中的所有文件”不会注入
任何内容),无存储的仓库保持静默(此处提示永远不会触发),并且
子代理会被跳过。horizon 在首次
触碰之后的轮次到达:仓库中的第一个操作在设计上就是无信息的。
- 会话结束: on_session_finalize →
horizon session-end --harness hermes --session --store (省略摘要,因此记录携带
summary: null;从未注入的会话回退到 cwd
发现)。网关在其 SIGINT/SIGTERM 关闭时运行 finalize,
处于 10 秒的 finalize 预算内——该 bin 需要毫秒级——因此
systemctl --user restart hermes-gateway(SIGTERM,然后仅在
单元的停止超时之后才 SIGKILL)仍会记录每个活动会话的记录;
硬杀或崩溃则不会。
6. ZCode
npm install -g deep-horizon # the CLI — see Install
适配器是随包一起提供的 POSIX sh 脚本
(adapters/zcode/session-start、adapters/zcode/pre-execute);钩子
配置直接从全局安装中运行它们,因此没有复制
步骤。它们需要 PATH 上有 jq 和 node。
horizon install --harness zcode 将下面的块写入用户
配置 ~/.zcode/cli/config.json(解析 → 合并 → 验证 → 备份 →
原子写入;参见 Wiring),并且 horizon doctor 之后会验证接线。
项目本地的 .zcode/config.json 可以改为携带
相同的块——此仓库检入了 SessionStart 那一半——但项目
钩子受信任门控,并且一旦在两个作用域都声明它们后受信任,就会双重触发,
因此请只选择一个表面;用户配置是可靠的那个:
{
"hooks": {
"enabled": true,
"events": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "\"$(npm root -g)/deep-horizon/adapters/zcode/session-start\"",
"enabled": true
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|Read|Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "\"$(npm root -g)/deep-horizon/adapters/zcode/pre-execute\"",
"enabled": true,
"timeout": 10
}
]
}
]
}
}
}
"enabled": true 是关键所在——ZCode 默认禁用配置文件钩子。
$(npm root -g) 展开发生在 ZCode 运行
带 command 的钩子;如果包安装在不同的全局前缀下(pnpm、bun),请将命令指向真实位置。timeout 以秒为单位;保持适度即可——钩子在工具调用之前内联运行。PreToolUse 匹配器是对工具名称区分大小写的正则表达式;Bash|Read|Edit|Write|NotebookEdit 被固定下来,因为这些调用的参数携带绝对目标(一个命令字符串、一个 file_path、一个 notebook_path),而该匹配器使钩子的开销不会落在那些永远不可能携带绝对目标的调用上。完全省略匹配器会匹配所有工具并且也能工作——脚本扫描的是字段,而不是工具名称——只是会为无谓的情况触发得更频繁。
启动:SessionStart 钩子匹配 startup 和 resume——每个应用实例都会向其会话交付一次 horizon。这种扩大是被一个已证实的事实所迫:additionalContext 信封永远不会持久化到会话历史中,因此在新应用实例中恢复的会话上下文中没有 horizon,而 resume 触发是唯一能重新交付它的接缝。(harness 每个应用实例运行一次钩子——一个 sessionStartHookRan 闩锁——因此匹配器的扩大不会循环。)一个 10 秒的同时性防护覆盖了闩锁无法覆盖的那一种竞态:两个应用实例在几秒内相继启动,都会为同一个会话 id 触发 startup——第二次触发发现其标记对刚刚被播种,于是保持静默;几分钟后重启会重新注入,这正是重点。钩子负载不携带子代理标记,因此没有子代理防护:如果子代理会话触发了钩子,它会像任何其他会话一样收到 horizon——是噪音,不是危害。每个钩子都失败开放:缺少 bin、缺少 jq 或任何错误都不会注入任何内容,也永远不会阻塞会话。每次触发都会向存储的 .horizon/hooks.log 追加一行(bin 触发日志,像 sessions.jsonl 一样被 gitignore)——startup 为 injected/nudged/silent,horizon session-end 为 recorded/error——这样无需取证即可发现回归。
参数触发:PreToolUse 钩子扫描每个匹配调用的参数以查找绝对目标目录——一个 workdir 参数、file_path/path 的 dirname、command 字符串内的绝对路径 token(包括 git -C 目标)。相对路径会被丢弃:钩子无法知道工具将在哪个 shell cwd 中运行,而猜测一个会相对于 harness 进程进行解析。对于带有存储的被触及目录,该目录的 horizon 会在会话中途通过同一个 additionalContext 信封注入,每个存储每个会话一次——钩子本身会在每次匹配调用时触发(已现场证实:两个相同的调用产生了两次注入),因此去重是脚本的职责,保存在 ~/.cache/deep-horizon/markers/ 下的每会话标记文件中,由启动钩子用启动存储播种。这个种子正是关键:一个在仓库 A 中启动、会话中途再次触及仓库 A 的会话不会两次获得仓库 A 的 horizon;而首次触及仓库 B 则会。缓存目录是有意为之——该
标记在重启后仍然存在,下一段依赖这一点。无存储目标只探测一次,被记为静默,之后不再询问。该钩子从不拒绝工具调用——它根本没有拒绝路径(exit 2 会拒绝;脚本无法产生该退出码)——并且以同样的方式失败开放:任何错误、缺少工具或缺少 bin 都意味着静默,调用不受影响地继续。与其他 harness 的参数触发器一样,子代理的工具调用与顶层调用不作区分:一个子代理触碰仓库 B,就会把仓库 B 的 horizon 带入它自己的会话——是噪音,不是危害。
项目范围的一次性信任步骤:在项目的 .zcode/config.json 中声明的钩子受信任门控——它们会一直处于阻塞状态(记录为 pending_trust),直到在 TUI 的审查流程中一次性授予工作区钩子信任;无头运行永远不会授予它。~/.zcode/cli/config.json 中的用户范围钩子无条件运行——如果你不想触碰信任提示,这是可靠的途径。
会话结束:ZCode 没有关闭钩子——Stop 在每个助手回合结束时触发——因此该职责由启动注入本身承担。当存在存储且负载携带会话 id 时,钩子会在组合块后附加一个仅 zcode 的尾部,告诉代理:当用户正在结束会话时,它应提供
horizon session-end --harness zcode --session
如果用户给出,则附上一行 --summary 说明会话做了什么;如果用户不给,则没有——记录随后携带 summary: null;摘要绝不编造。如果用户拒绝,代理就让它过去。该尾部仅当工作目录上方某处存在存储时才会附加(无存储的组合为空,不注入任何内容),并且仅当负载携带会话 id 时才会附加:没有该 id,代理无法命名记录,而占位 id 会写入错误的 id。
该指令是质量路径,而非承重路径——实时数据表明,代理理解该职责,在会话中途正确地推迟它,然后会话就直接结束,没有收尾交流,因此该提议从未触发。承重机制是启动对账:每次会话启动还会扫描标记目录,查找先前会话的标记,这些标记命名了本次触发对应的存储、早于 60 秒、且在存储的 sessions.jsonl 中没有记录——并自行写入每个孤儿的记录(horizon session-end --harness zcode --session --store ,摘要诚实地为 null)。存储自身的 once-guard 使其幂等;60 秒保护将活跃的并发会话排除在外。这也覆盖了任何钩子都无法看到的信号死亡:被 SIGINT/SIGTERM 杀死的会话——或关闭的终端——会留下一个标记,该项目中的下一个会话会将其埋葬。
已知限制,直说:对账仅在项目中某个会话再次启动时运行——一个其每个会话都在任何下一次启动之前死掉的项目,在那之前一直不被记录。而且不存在真正的关闭钩子:它的钩子
事件数量恰好为七个(SessionStart、UserPromptSubmit、PreToolUse、
PermissionRequest、PostToolUse、PostToolUseFailure、Stop),其中没有
一个是退出钩子,而在 SIGINT/SIGTERM 时,二进制文件自身的关闭
完全不运行任何用户可触及的代码。
手动使用,无需全局安装
node ./deep-horizon/bin/horizon.js show
node ./deep-horizon/bin/horizon-inject.js --harness
(这些二进制文件会导入 dist/,所以请先在克隆的仓库中运行 npm install —— 它的
prepare 会构建它。npx -p deep-horizon 会解析注册表,而注册表中
还没有这样的包。)
许可证
MIT