odai-dsh-agent 0.2.22 → 0.2.23
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/LICENSE +21 -21
- package/README.md +155 -157
- package/build/bin/odai-dsh-agent.mjs +18 -36
- package/build/bin/odai-dsh-agent.mjs.map +1 -1
- package/build/src/installer.d.mts +1 -6
- package/build/src/installer.d.mts.map +1 -1
- package/build/src/installer.mjs +3 -82
- package/build/src/installer.mjs.map +1 -1
- package/client/client.js +36 -58
- package/control-center.cordis.patch.yml +3 -3
- package/package.json +90 -90
- package/preset/odai/agent.cordis.yml +260 -260
- package/preset/odai/preset.yml +3 -3
- package/preset/odai/runtime/control-center-runtime.d.mts +1 -0
- package/preset/odai/runtime/control-center-runtime.d.mts.map +1 -1
- package/preset/odai/runtime/control-center-runtime.mjs +20 -0
- package/preset/odai/runtime/control-center-runtime.mjs.map +1 -1
- package/preset/odai/runtime/index.mjs +3 -4
- package/preset/odai/runtime/index.mjs.map +1 -1
- package/preset/odai/runtime/{session-compat.d.mts → legacy-session-repair.d.mts} +7 -7
- package/preset/odai/runtime/legacy-session-repair.d.mts.map +1 -0
- package/preset/odai/runtime/{session-compat.mjs → legacy-session-repair.mjs} +1 -1
- package/preset/odai/runtime/legacy-session-repair.mjs.map +1 -0
- package/preset/odai/runtime/role-overlays.mjs +8 -8
- package/preset/odai/runtime/routing-context.d.mts +1 -1
- package/preset/odai/runtime/routing-context.d.mts.map +1 -1
- package/preset/odai/runtime/runtime-support.d.mts +0 -1
- package/preset/odai/runtime/runtime-support.d.mts.map +1 -1
- package/preset/odai/runtime/runtime-support.mjs.map +1 -1
- package/preset/odai/runtime/runtime-types.d.mts +1 -3
- package/preset/odai/runtime/runtime-types.d.mts.map +1 -1
- package/preset/odai/runtime/runtime-types.mjs +1 -5
- package/preset/odai/runtime/runtime-types.mjs.map +1 -1
- package/preset/odai/runtime/session-evidence.d.mts.map +1 -1
- package/preset/odai/runtime/session-evidence.mjs +1 -3
- package/preset/odai/runtime/session-evidence.mjs.map +1 -1
- package/preset/odai/skills/odai/SKILL.md +68 -68
- package/preset/odai/skills/odai/agents/openai.yaml +4 -4
- package/preset/odai/skills/odai/assets/claude-agents/agent.md +11 -11
- package/preset/odai/skills/odai/assets/codex-agents/config.toml +11 -11
- package/preset/odai/skills/odai/assets/codex-agents/role.toml +5 -5
- package/preset/odai/skills/odai/assets/copilot-agents/agent.md +13 -13
- package/preset/odai/skills/odai/assets/hooks-policy.example.json +24 -24
- package/preset/odai/skills/odai/assets/routing-roles/controller.md +15 -15
- package/preset/odai/skills/odai/assets/routing-roles/frontend.md +7 -7
- package/preset/odai/skills/odai/assets/routing-roles/planner.md +28 -28
- package/preset/odai/skills/odai/assets/routing-roles/researcher.md +6 -6
- package/preset/odai/skills/odai/assets/routing-roles/reviewer.md +11 -11
- package/preset/odai/skills/odai/assets/task-state.md +14 -14
- package/preset/odai/skills/odai/manifest.json +52 -52
- package/preset/odai/skills/odai/references/craft.md +54 -54
- package/preset/odai/skills/odai/references/dao.md +31 -31
- package/preset/odai/skills/odai/references/leverage.md +50 -50
- package/preset/odai/skills/odai/references/planning.md +83 -83
- package/preset/odai/skills/odai/references/support.md +30 -30
- package/preset/odai/skills/odai/references/verification.md +24 -24
- package/preset/odai/skills/odai/scripts/build-hooks.mjs +273 -273
- package/preset/odai/skills/odai/scripts/build-routing.mjs +315 -315
- package/preset/odai/skills/odai/scripts/install-routing.mjs +393 -393
- package/preset/odai/skills/odai/scripts/odai-hook.mjs +457 -457
- package/preset/odai/skills/odai/scripts/run-role.mjs +285 -285
- package/preset/odai/skills/odai/scripts/verify-routing.mjs +103 -103
- package/preset/odai/runtime/session-compat.d.mts.map +0 -1
- package/preset/odai/runtime/session-compat.mjs.map +0 -1
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# 验证与完成
|
|
2
|
-
|
|
3
|
-
需要设计验收、判断完成、继续旧任务或在环境受限时收口时读取。
|
|
4
|
-
|
|
5
|
-
## 建立验收
|
|
6
|
-
|
|
7
|
-
- 先确认验收对应用户已对齐的真实结果,而非模型自行选择的根因、手段或交付物;再把明确结果、限制、具体对象、输入、位置和不可接受结果映射成可观察证据,证据证明为目标所必需的条件也纳入。
|
|
8
|
-
- 只验证相关属性和主要回归风险。测试、构建、静态检查、diff、截图、日志和人工观察各自只证明实际覆盖的内容,不能互相冒充。
|
|
9
|
-
- 项目已指定某项事实的权威筛选、查询、命令、样本或来源时,按该入口核验;相似字段、近似范围、替代命令或其他来源不能冒充同一对象与含义。
|
|
10
|
-
- 验证强度由失败代价、影响范围、不确定性和已有证据覆盖共同决定;用户可见、工具可用或追求完整本身不构成升级理由。较轻证据已覆盖目标与主要风险时即采用;只有关键属性仍无法判定,或出现失败、冲突与新风险时,才升级到更接近真实场景的验证。
|
|
11
|
-
- 验收先写要证明的属性,不把模型自行选择的命令、工具或流程升级成成功条件;只有用户或权威约定明确点名时,指定方法本身才属于契约。指定验证器因当前启动入口或依赖不可用而失败时,先核对现有环境能否用兼容入口运行同一验证器;仍不可用才采用真正覆盖同一属性的其他证据,并明确它没有证明原验证器已通过,不把自写近似检查称为等价。证据仍缺失就如实标明。
|
|
12
|
-
|
|
13
|
-
## 判断完成
|
|
14
|
-
|
|
15
|
-
- 用户已对齐的真实结果已经成立,明确要求均有结果,主要风险有相称证据,才算完成。局部未知只阻断依赖它的部分,其余继续交付。
|
|
16
|
-
- 声明、配置和注释只证明“写了什么”;局部观测只证明观测范围;真实调用或使用链才证明端到端结果。
|
|
17
|
-
- 两个或更多写入批次共同组成一个交付物时,组件完成不具有传递性。主流程只在唯一组合状态上核对实际改动范围、共享契约、依赖顺序和完整验收;各批次分别通过或分别受审不能替代组合证据。
|
|
18
|
-
- 组合时出现的冲突解决或兼容性编辑属于新的获授权写入,须有自己的范围和验收。用户要求独立复核,或组合跨越重大安全、数据、并发、兼容、迁移或公共契约边界时,对组合状态进行独立复核。
|
|
19
|
-
- 改动落地但关键场景未验证时,明确区分已实施、已验证与未验证,不用“应该、看起来、理论上”表达成功。
|
|
20
|
-
- 实际结果或用户反馈与原判断不符时,将其视为新证据,修正结论、验收或后续做法,并让受影响的旧证据与完成判断失效;不把已说过的话当成必须维护的立场。
|
|
21
|
-
- 版本、候选、阶段、提交、发布或迁移状态属于可验证结果。先核对机器可读事实源、当前产物与真实事件;前一状态未实际结束时更新同一状态,不创建后继值或把当前候选写成历史。确需迁移时,核对权威载体与用户文档指向同一当前值,历史记录按事实保留但不与现役状态争夺所有权。
|
|
22
|
-
- 证据足够就停止;每次追加验证前须有尚未闭合、且结果会改变完成判断的具体属性。同一证据范围已通过且没有新失败、冲突、变更或风险时,不重复验证;不能因安心、工具可用、发现范围外问题或追求完整无限提高完成门,也不能因疲劳或流程完成降低验收。
|
|
23
|
-
|
|
24
|
-
继续旧任务时,先从用户原话、当前产物、主状态、diff 和真实运行结果恢复目标与进度;跳过已有有效证据的完成项,不沿用无证据的旧完成标签。
|
|
1
|
+
# 验证与完成
|
|
2
|
+
|
|
3
|
+
需要设计验收、判断完成、继续旧任务或在环境受限时收口时读取。
|
|
4
|
+
|
|
5
|
+
## 建立验收
|
|
6
|
+
|
|
7
|
+
- 先确认验收对应用户已对齐的真实结果,而非模型自行选择的根因、手段或交付物;再把明确结果、限制、具体对象、输入、位置和不可接受结果映射成可观察证据,证据证明为目标所必需的条件也纳入。
|
|
8
|
+
- 只验证相关属性和主要回归风险。测试、构建、静态检查、diff、截图、日志和人工观察各自只证明实际覆盖的内容,不能互相冒充。
|
|
9
|
+
- 项目已指定某项事实的权威筛选、查询、命令、样本或来源时,按该入口核验;相似字段、近似范围、替代命令或其他来源不能冒充同一对象与含义。
|
|
10
|
+
- 验证强度由失败代价、影响范围、不确定性和已有证据覆盖共同决定;用户可见、工具可用或追求完整本身不构成升级理由。较轻证据已覆盖目标与主要风险时即采用;只有关键属性仍无法判定,或出现失败、冲突与新风险时,才升级到更接近真实场景的验证。
|
|
11
|
+
- 验收先写要证明的属性,不把模型自行选择的命令、工具或流程升级成成功条件;只有用户或权威约定明确点名时,指定方法本身才属于契约。指定验证器因当前启动入口或依赖不可用而失败时,先核对现有环境能否用兼容入口运行同一验证器;仍不可用才采用真正覆盖同一属性的其他证据,并明确它没有证明原验证器已通过,不把自写近似检查称为等价。证据仍缺失就如实标明。
|
|
12
|
+
|
|
13
|
+
## 判断完成
|
|
14
|
+
|
|
15
|
+
- 用户已对齐的真实结果已经成立,明确要求均有结果,主要风险有相称证据,才算完成。局部未知只阻断依赖它的部分,其余继续交付。
|
|
16
|
+
- 声明、配置和注释只证明“写了什么”;局部观测只证明观测范围;真实调用或使用链才证明端到端结果。
|
|
17
|
+
- 两个或更多写入批次共同组成一个交付物时,组件完成不具有传递性。主流程只在唯一组合状态上核对实际改动范围、共享契约、依赖顺序和完整验收;各批次分别通过或分别受审不能替代组合证据。
|
|
18
|
+
- 组合时出现的冲突解决或兼容性编辑属于新的获授权写入,须有自己的范围和验收。用户要求独立复核,或组合跨越重大安全、数据、并发、兼容、迁移或公共契约边界时,对组合状态进行独立复核。
|
|
19
|
+
- 改动落地但关键场景未验证时,明确区分已实施、已验证与未验证,不用“应该、看起来、理论上”表达成功。
|
|
20
|
+
- 实际结果或用户反馈与原判断不符时,将其视为新证据,修正结论、验收或后续做法,并让受影响的旧证据与完成判断失效;不把已说过的话当成必须维护的立场。
|
|
21
|
+
- 版本、候选、阶段、提交、发布或迁移状态属于可验证结果。先核对机器可读事实源、当前产物与真实事件;前一状态未实际结束时更新同一状态,不创建后继值或把当前候选写成历史。确需迁移时,核对权威载体与用户文档指向同一当前值,历史记录按事实保留但不与现役状态争夺所有权。
|
|
22
|
+
- 证据足够就停止;每次追加验证前须有尚未闭合、且结果会改变完成判断的具体属性。同一证据范围已通过且没有新失败、冲突、变更或风险时,不重复验证;不能因安心、工具可用、发现范围外问题或追求完整无限提高完成门,也不能因疲劳或流程完成降低验收。
|
|
23
|
+
|
|
24
|
+
继续旧任务时,先从用户原话、当前产物、主状态、diff 和真实运行结果恢复目标与进度;跳过已有有效证据的完成项,不沿用无证据的旧完成标签。
|
|
@@ -1,273 +1,273 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
copyFileSync,
|
|
5
|
-
existsSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readdirSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
} from "node:fs";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
|
|
13
|
-
const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
-
const runtimeSource = path.join(skillRoot, "scripts", "odai-hook.mjs");
|
|
15
|
-
const policySource = path.join(skillRoot, "assets", "hooks-policy.example.json");
|
|
16
|
-
const supportedHosts = ["codex", "claude", "copilot", "gemini", "grok", "kimi"];
|
|
17
|
-
|
|
18
|
-
const args = parseArgs(process.argv.slice(2));
|
|
19
|
-
if (args.help) {
|
|
20
|
-
printHelp();
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
if (!args.out) fail("缺少 --out <directory>");
|
|
24
|
-
if (!args.host) fail("缺少 --host <codex|claude|copilot|gemini|grok|kimi|all>");
|
|
25
|
-
|
|
26
|
-
const hosts = args.host === "all" ? supportedHosts : [args.host];
|
|
27
|
-
for (const host of hosts) {
|
|
28
|
-
if (!supportedHosts.includes(host)) fail(`不支持的 host:${host}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const outputRoot = path.resolve(args.out);
|
|
32
|
-
mkdirSync(outputRoot, { recursive: true });
|
|
33
|
-
for (const host of hosts) {
|
|
34
|
-
const target = path.join(outputRoot, host);
|
|
35
|
-
assertEmpty(target);
|
|
36
|
-
mkdirSync(target, { recursive: true });
|
|
37
|
-
buildAdapter(host, target);
|
|
38
|
-
console.log(`${host}: ${target}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function buildAdapter(host, target) {
|
|
42
|
-
const metadata = {
|
|
43
|
-
version: 1,
|
|
44
|
-
host,
|
|
45
|
-
policyPath: "<project>/.odai/hooks.json",
|
|
46
|
-
defaultBehavior: "No policy file means no-op.",
|
|
47
|
-
generatedFrom: "skills/odai/scripts/build-hooks.mjs",
|
|
48
|
-
capabilities: host === "grok" ? ["pre-tool"] : ["pre-tool", "stop-check"],
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if (host === "copilot") {
|
|
52
|
-
copy(target, ".odai/hooks/odai-hook.mjs", runtimeSource);
|
|
53
|
-
copy(target, ".odai/hooks.example.json", policySource);
|
|
54
|
-
writeJson(target, ".github/hooks/odai.json", copilotHooks());
|
|
55
|
-
metadata.format = "project-overlay";
|
|
56
|
-
metadata.install = "Merge this directory into the repository root, review .github/hooks/odai.json, then create .odai/hooks.json from the example.";
|
|
57
|
-
} else {
|
|
58
|
-
copy(target, "scripts/odai-hook.mjs", runtimeSource);
|
|
59
|
-
copy(target, "examples/hooks.json", policySource);
|
|
60
|
-
|
|
61
|
-
if (host === "codex") {
|
|
62
|
-
writeJson(target, ".codex-plugin/plugin.json", codexPluginManifest());
|
|
63
|
-
writeJson(target, "hooks/hooks.json", groupedHooks("${PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
|
|
64
|
-
metadata.format = "codex-plugin";
|
|
65
|
-
metadata.install = "Install or link this directory as a Codex plugin, review and trust its hooks, then create <project>/.odai/hooks.json.";
|
|
66
|
-
} else if (host === "claude") {
|
|
67
|
-
writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Claude Code"));
|
|
68
|
-
writeJson(target, "hooks/hooks.json", groupedHooks("${CLAUDE_PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
|
|
69
|
-
metadata.format = "claude-plugin";
|
|
70
|
-
metadata.install = "Install this directory as a Claude Code plugin, then create <project>/.odai/hooks.json.";
|
|
71
|
-
} else if (host === "grok") {
|
|
72
|
-
writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Grok Build"));
|
|
73
|
-
writeJson(target, "hooks/hooks.json", groupedHooks("${GROK_PLUGIN_ROOT}", host, "PreToolUse", null));
|
|
74
|
-
metadata.format = "grok-plugin-using-claude-compatible-manifest";
|
|
75
|
-
metadata.install = "Install this directory with Grok Build's plugin manager, trust the hook, then create <project>/.odai/hooks.json.";
|
|
76
|
-
metadata.note = "Grok Build exposes PreToolUse as the blocking boundary; this adapter intentionally has no Stop verifier.";
|
|
77
|
-
} else if (host === "gemini") {
|
|
78
|
-
writeJson(target, "gemini-extension.json", {
|
|
79
|
-
name: "odai-hooks",
|
|
80
|
-
version: "0.1.0",
|
|
81
|
-
description: "Optional deterministic boundaries for odai.",
|
|
82
|
-
});
|
|
83
|
-
writeJson(target, "hooks/hooks.json", groupedHooks("${extensionPath}", host, "BeforeTool", "AfterAgent"));
|
|
84
|
-
metadata.format = "gemini-extension";
|
|
85
|
-
metadata.install = "Link or install this directory as a Gemini CLI extension, then create <project>/.odai/hooks.json.";
|
|
86
|
-
} else if (host === "kimi") {
|
|
87
|
-
writeJson(target, "kimi.plugin.json", kimiManifest());
|
|
88
|
-
metadata.format = "kimi-plugin";
|
|
89
|
-
metadata.install = "Run /plugins install <this-directory> in Kimi Code CLI, enable it, then create <project>/.odai/hooks.json.";
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
writeJson(target, "ADAPTER.json", metadata);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function groupedHooks(rootVariable, host, preEvent, stopEvent) {
|
|
97
|
-
const commandRoot = `${rootVariable}/scripts/odai-hook.mjs`;
|
|
98
|
-
const preHandler = {
|
|
99
|
-
type: "command",
|
|
100
|
-
command: `node "${commandRoot}" pre-tool --host ${host}`,
|
|
101
|
-
timeout: 5,
|
|
102
|
-
};
|
|
103
|
-
const stopHandler = {
|
|
104
|
-
type: "command",
|
|
105
|
-
command: `node "${commandRoot}" stop --host ${host}`,
|
|
106
|
-
timeout: 600,
|
|
107
|
-
};
|
|
108
|
-
if (host === "gemini") {
|
|
109
|
-
preHandler.name = "odai-write-boundary";
|
|
110
|
-
stopHandler.name = "odai-acceptance-check";
|
|
111
|
-
}
|
|
112
|
-
const hooks = {
|
|
113
|
-
[preEvent]: [
|
|
114
|
-
{
|
|
115
|
-
matcher: writeMatcher(host),
|
|
116
|
-
hooks: [preHandler],
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
};
|
|
120
|
-
if (stopEvent) {
|
|
121
|
-
hooks[stopEvent] = [
|
|
122
|
-
{
|
|
123
|
-
matcher: "*",
|
|
124
|
-
hooks: [stopHandler],
|
|
125
|
-
},
|
|
126
|
-
];
|
|
127
|
-
}
|
|
128
|
-
const result = { hooks };
|
|
129
|
-
if (host !== "gemini") {
|
|
130
|
-
result.description = "Optional odai boundaries. Inactive unless the project defines .odai/hooks.json.";
|
|
131
|
-
}
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function copilotHooks() {
|
|
136
|
-
const command = "node .odai/hooks/odai-hook.mjs";
|
|
137
|
-
return {
|
|
138
|
-
version: 1,
|
|
139
|
-
hooks: {
|
|
140
|
-
preToolUse: [
|
|
141
|
-
{
|
|
142
|
-
type: "command",
|
|
143
|
-
matcher: writeMatcher("copilot"),
|
|
144
|
-
command: `${command} pre-tool --host copilot`,
|
|
145
|
-
cwd: ".",
|
|
146
|
-
timeoutSec: 5,
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
agentStop: [
|
|
150
|
-
{
|
|
151
|
-
type: "command",
|
|
152
|
-
command: `${command} stop --host copilot`,
|
|
153
|
-
cwd: ".",
|
|
154
|
-
timeoutSec: 600,
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function kimiManifest() {
|
|
162
|
-
return {
|
|
163
|
-
name: "odai-hooks",
|
|
164
|
-
version: "0.1.0",
|
|
165
|
-
description: "Optional deterministic boundaries for odai.",
|
|
166
|
-
interface: {
|
|
167
|
-
displayName: "odai Hooks",
|
|
168
|
-
shortDescription: "Protect explicit read-only paths and run declared acceptance checks.",
|
|
169
|
-
},
|
|
170
|
-
hooks: [
|
|
171
|
-
{
|
|
172
|
-
event: "PreToolUse",
|
|
173
|
-
matcher: writeMatcher("kimi"),
|
|
174
|
-
command: "node ./scripts/odai-hook.mjs pre-tool --host kimi",
|
|
175
|
-
timeout: 5,
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
event: "Stop",
|
|
179
|
-
command: "node ./scripts/odai-hook.mjs stop --host kimi",
|
|
180
|
-
timeout: 600,
|
|
181
|
-
},
|
|
182
|
-
],
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function pluginManifest(hostName) {
|
|
187
|
-
return {
|
|
188
|
-
name: "odai-hooks",
|
|
189
|
-
version: "0.1.0",
|
|
190
|
-
description: `Optional deterministic odai boundaries for ${hostName}.`,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function codexPluginManifest() {
|
|
195
|
-
return {
|
|
196
|
-
id: "odai-hooks",
|
|
197
|
-
name: "odai-hooks",
|
|
198
|
-
version: "0.1.0",
|
|
199
|
-
description: "Optional deterministic boundaries for odai.",
|
|
200
|
-
author: {
|
|
201
|
-
name: "orzi",
|
|
202
|
-
url: "https://github.com/orziz",
|
|
203
|
-
},
|
|
204
|
-
homepage: "https://github.com/orziz/odai",
|
|
205
|
-
repository: "https://github.com/orziz/odai",
|
|
206
|
-
license: "MIT",
|
|
207
|
-
keywords: ["odai", "hooks", "guardrails"],
|
|
208
|
-
interface: {
|
|
209
|
-
displayName: "odai Hooks",
|
|
210
|
-
shortDescription: "Protect explicit read-only paths and run declared checks.",
|
|
211
|
-
longDescription: "Optional deterministic hook guardrails for projects that use odai.",
|
|
212
|
-
developerName: "orzi",
|
|
213
|
-
category: "Developer Tools",
|
|
214
|
-
capabilities: ["PreToolUse guard", "Stop acceptance check"],
|
|
215
|
-
websiteURL: "https://github.com/orziz/odai",
|
|
216
|
-
brandColor: "#1F6FEB",
|
|
217
|
-
defaultPrompt: "Use odai with the project's explicit hook boundaries.",
|
|
218
|
-
},
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function writeMatcher(host) {
|
|
223
|
-
if (host === "copilot") return "create|edit|str_replace_editor|apply_patch";
|
|
224
|
-
if (host === "gemini") return "write_file|replace|apply_patch|edit|create";
|
|
225
|
-
return "Edit|Write|apply_patch|create|edit|write_file|str_replace_editor";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function writeJson(root, relativePath, value) {
|
|
229
|
-
const target = path.join(root, relativePath);
|
|
230
|
-
mkdirSync(path.dirname(target), { recursive: true });
|
|
231
|
-
writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function copy(root, relativePath, source) {
|
|
235
|
-
const target = path.join(root, relativePath);
|
|
236
|
-
mkdirSync(path.dirname(target), { recursive: true });
|
|
237
|
-
copyFileSync(source, target);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function assertEmpty(target) {
|
|
241
|
-
if (!existsSync(target)) return;
|
|
242
|
-
if (readdirSync(target).length > 0) fail(`输出目录非空,拒绝覆盖:${target}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function parseArgs(argv) {
|
|
246
|
-
const result = { host: "", out: "", help: false };
|
|
247
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
248
|
-
const value = argv[index];
|
|
249
|
-
if (value === "--help" || value === "-h") result.help = true;
|
|
250
|
-
else if (value === "--host") result.host = argv[++index] || "";
|
|
251
|
-
else if (value === "--out") result.out = argv[++index] || "";
|
|
252
|
-
else fail(`未知参数:${value}`);
|
|
253
|
-
}
|
|
254
|
-
return result;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function printHelp() {
|
|
258
|
-
console.log(`Build optional odai hook adapters.
|
|
259
|
-
|
|
260
|
-
Usage:
|
|
261
|
-
node skills/odai/scripts/build-hooks.mjs --host <host|all> --out <directory>
|
|
262
|
-
|
|
263
|
-
Hosts:
|
|
264
|
-
${supportedHosts.join(", ")}
|
|
265
|
-
|
|
266
|
-
The builder never installs hooks or edits a target project. Each generated
|
|
267
|
-
adapter is inactive until that project defines .odai/hooks.json.`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function fail(message) {
|
|
271
|
-
console.error(message);
|
|
272
|
-
process.exit(1);
|
|
273
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
copyFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
+
const runtimeSource = path.join(skillRoot, "scripts", "odai-hook.mjs");
|
|
15
|
+
const policySource = path.join(skillRoot, "assets", "hooks-policy.example.json");
|
|
16
|
+
const supportedHosts = ["codex", "claude", "copilot", "gemini", "grok", "kimi"];
|
|
17
|
+
|
|
18
|
+
const args = parseArgs(process.argv.slice(2));
|
|
19
|
+
if (args.help) {
|
|
20
|
+
printHelp();
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
if (!args.out) fail("缺少 --out <directory>");
|
|
24
|
+
if (!args.host) fail("缺少 --host <codex|claude|copilot|gemini|grok|kimi|all>");
|
|
25
|
+
|
|
26
|
+
const hosts = args.host === "all" ? supportedHosts : [args.host];
|
|
27
|
+
for (const host of hosts) {
|
|
28
|
+
if (!supportedHosts.includes(host)) fail(`不支持的 host:${host}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const outputRoot = path.resolve(args.out);
|
|
32
|
+
mkdirSync(outputRoot, { recursive: true });
|
|
33
|
+
for (const host of hosts) {
|
|
34
|
+
const target = path.join(outputRoot, host);
|
|
35
|
+
assertEmpty(target);
|
|
36
|
+
mkdirSync(target, { recursive: true });
|
|
37
|
+
buildAdapter(host, target);
|
|
38
|
+
console.log(`${host}: ${target}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildAdapter(host, target) {
|
|
42
|
+
const metadata = {
|
|
43
|
+
version: 1,
|
|
44
|
+
host,
|
|
45
|
+
policyPath: "<project>/.odai/hooks.json",
|
|
46
|
+
defaultBehavior: "No policy file means no-op.",
|
|
47
|
+
generatedFrom: "skills/odai/scripts/build-hooks.mjs",
|
|
48
|
+
capabilities: host === "grok" ? ["pre-tool"] : ["pre-tool", "stop-check"],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (host === "copilot") {
|
|
52
|
+
copy(target, ".odai/hooks/odai-hook.mjs", runtimeSource);
|
|
53
|
+
copy(target, ".odai/hooks.example.json", policySource);
|
|
54
|
+
writeJson(target, ".github/hooks/odai.json", copilotHooks());
|
|
55
|
+
metadata.format = "project-overlay";
|
|
56
|
+
metadata.install = "Merge this directory into the repository root, review .github/hooks/odai.json, then create .odai/hooks.json from the example.";
|
|
57
|
+
} else {
|
|
58
|
+
copy(target, "scripts/odai-hook.mjs", runtimeSource);
|
|
59
|
+
copy(target, "examples/hooks.json", policySource);
|
|
60
|
+
|
|
61
|
+
if (host === "codex") {
|
|
62
|
+
writeJson(target, ".codex-plugin/plugin.json", codexPluginManifest());
|
|
63
|
+
writeJson(target, "hooks/hooks.json", groupedHooks("${PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
|
|
64
|
+
metadata.format = "codex-plugin";
|
|
65
|
+
metadata.install = "Install or link this directory as a Codex plugin, review and trust its hooks, then create <project>/.odai/hooks.json.";
|
|
66
|
+
} else if (host === "claude") {
|
|
67
|
+
writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Claude Code"));
|
|
68
|
+
writeJson(target, "hooks/hooks.json", groupedHooks("${CLAUDE_PLUGIN_ROOT}", host, "PreToolUse", "Stop"));
|
|
69
|
+
metadata.format = "claude-plugin";
|
|
70
|
+
metadata.install = "Install this directory as a Claude Code plugin, then create <project>/.odai/hooks.json.";
|
|
71
|
+
} else if (host === "grok") {
|
|
72
|
+
writeJson(target, ".claude-plugin/plugin.json", pluginManifest("Grok Build"));
|
|
73
|
+
writeJson(target, "hooks/hooks.json", groupedHooks("${GROK_PLUGIN_ROOT}", host, "PreToolUse", null));
|
|
74
|
+
metadata.format = "grok-plugin-using-claude-compatible-manifest";
|
|
75
|
+
metadata.install = "Install this directory with Grok Build's plugin manager, trust the hook, then create <project>/.odai/hooks.json.";
|
|
76
|
+
metadata.note = "Grok Build exposes PreToolUse as the blocking boundary; this adapter intentionally has no Stop verifier.";
|
|
77
|
+
} else if (host === "gemini") {
|
|
78
|
+
writeJson(target, "gemini-extension.json", {
|
|
79
|
+
name: "odai-hooks",
|
|
80
|
+
version: "0.1.0",
|
|
81
|
+
description: "Optional deterministic boundaries for odai.",
|
|
82
|
+
});
|
|
83
|
+
writeJson(target, "hooks/hooks.json", groupedHooks("${extensionPath}", host, "BeforeTool", "AfterAgent"));
|
|
84
|
+
metadata.format = "gemini-extension";
|
|
85
|
+
metadata.install = "Link or install this directory as a Gemini CLI extension, then create <project>/.odai/hooks.json.";
|
|
86
|
+
} else if (host === "kimi") {
|
|
87
|
+
writeJson(target, "kimi.plugin.json", kimiManifest());
|
|
88
|
+
metadata.format = "kimi-plugin";
|
|
89
|
+
metadata.install = "Run /plugins install <this-directory> in Kimi Code CLI, enable it, then create <project>/.odai/hooks.json.";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
writeJson(target, "ADAPTER.json", metadata);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function groupedHooks(rootVariable, host, preEvent, stopEvent) {
|
|
97
|
+
const commandRoot = `${rootVariable}/scripts/odai-hook.mjs`;
|
|
98
|
+
const preHandler = {
|
|
99
|
+
type: "command",
|
|
100
|
+
command: `node "${commandRoot}" pre-tool --host ${host}`,
|
|
101
|
+
timeout: 5,
|
|
102
|
+
};
|
|
103
|
+
const stopHandler = {
|
|
104
|
+
type: "command",
|
|
105
|
+
command: `node "${commandRoot}" stop --host ${host}`,
|
|
106
|
+
timeout: 600,
|
|
107
|
+
};
|
|
108
|
+
if (host === "gemini") {
|
|
109
|
+
preHandler.name = "odai-write-boundary";
|
|
110
|
+
stopHandler.name = "odai-acceptance-check";
|
|
111
|
+
}
|
|
112
|
+
const hooks = {
|
|
113
|
+
[preEvent]: [
|
|
114
|
+
{
|
|
115
|
+
matcher: writeMatcher(host),
|
|
116
|
+
hooks: [preHandler],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
if (stopEvent) {
|
|
121
|
+
hooks[stopEvent] = [
|
|
122
|
+
{
|
|
123
|
+
matcher: "*",
|
|
124
|
+
hooks: [stopHandler],
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
const result = { hooks };
|
|
129
|
+
if (host !== "gemini") {
|
|
130
|
+
result.description = "Optional odai boundaries. Inactive unless the project defines .odai/hooks.json.";
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function copilotHooks() {
|
|
136
|
+
const command = "node .odai/hooks/odai-hook.mjs";
|
|
137
|
+
return {
|
|
138
|
+
version: 1,
|
|
139
|
+
hooks: {
|
|
140
|
+
preToolUse: [
|
|
141
|
+
{
|
|
142
|
+
type: "command",
|
|
143
|
+
matcher: writeMatcher("copilot"),
|
|
144
|
+
command: `${command} pre-tool --host copilot`,
|
|
145
|
+
cwd: ".",
|
|
146
|
+
timeoutSec: 5,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
agentStop: [
|
|
150
|
+
{
|
|
151
|
+
type: "command",
|
|
152
|
+
command: `${command} stop --host copilot`,
|
|
153
|
+
cwd: ".",
|
|
154
|
+
timeoutSec: 600,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function kimiManifest() {
|
|
162
|
+
return {
|
|
163
|
+
name: "odai-hooks",
|
|
164
|
+
version: "0.1.0",
|
|
165
|
+
description: "Optional deterministic boundaries for odai.",
|
|
166
|
+
interface: {
|
|
167
|
+
displayName: "odai Hooks",
|
|
168
|
+
shortDescription: "Protect explicit read-only paths and run declared acceptance checks.",
|
|
169
|
+
},
|
|
170
|
+
hooks: [
|
|
171
|
+
{
|
|
172
|
+
event: "PreToolUse",
|
|
173
|
+
matcher: writeMatcher("kimi"),
|
|
174
|
+
command: "node ./scripts/odai-hook.mjs pre-tool --host kimi",
|
|
175
|
+
timeout: 5,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
event: "Stop",
|
|
179
|
+
command: "node ./scripts/odai-hook.mjs stop --host kimi",
|
|
180
|
+
timeout: 600,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pluginManifest(hostName) {
|
|
187
|
+
return {
|
|
188
|
+
name: "odai-hooks",
|
|
189
|
+
version: "0.1.0",
|
|
190
|
+
description: `Optional deterministic odai boundaries for ${hostName}.`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function codexPluginManifest() {
|
|
195
|
+
return {
|
|
196
|
+
id: "odai-hooks",
|
|
197
|
+
name: "odai-hooks",
|
|
198
|
+
version: "0.1.0",
|
|
199
|
+
description: "Optional deterministic boundaries for odai.",
|
|
200
|
+
author: {
|
|
201
|
+
name: "orzi",
|
|
202
|
+
url: "https://github.com/orziz",
|
|
203
|
+
},
|
|
204
|
+
homepage: "https://github.com/orziz/odai",
|
|
205
|
+
repository: "https://github.com/orziz/odai",
|
|
206
|
+
license: "MIT",
|
|
207
|
+
keywords: ["odai", "hooks", "guardrails"],
|
|
208
|
+
interface: {
|
|
209
|
+
displayName: "odai Hooks",
|
|
210
|
+
shortDescription: "Protect explicit read-only paths and run declared checks.",
|
|
211
|
+
longDescription: "Optional deterministic hook guardrails for projects that use odai.",
|
|
212
|
+
developerName: "orzi",
|
|
213
|
+
category: "Developer Tools",
|
|
214
|
+
capabilities: ["PreToolUse guard", "Stop acceptance check"],
|
|
215
|
+
websiteURL: "https://github.com/orziz/odai",
|
|
216
|
+
brandColor: "#1F6FEB",
|
|
217
|
+
defaultPrompt: "Use odai with the project's explicit hook boundaries.",
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function writeMatcher(host) {
|
|
223
|
+
if (host === "copilot") return "create|edit|str_replace_editor|apply_patch";
|
|
224
|
+
if (host === "gemini") return "write_file|replace|apply_patch|edit|create";
|
|
225
|
+
return "Edit|Write|apply_patch|create|edit|write_file|str_replace_editor";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function writeJson(root, relativePath, value) {
|
|
229
|
+
const target = path.join(root, relativePath);
|
|
230
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
231
|
+
writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function copy(root, relativePath, source) {
|
|
235
|
+
const target = path.join(root, relativePath);
|
|
236
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
237
|
+
copyFileSync(source, target);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function assertEmpty(target) {
|
|
241
|
+
if (!existsSync(target)) return;
|
|
242
|
+
if (readdirSync(target).length > 0) fail(`输出目录非空,拒绝覆盖:${target}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function parseArgs(argv) {
|
|
246
|
+
const result = { host: "", out: "", help: false };
|
|
247
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
248
|
+
const value = argv[index];
|
|
249
|
+
if (value === "--help" || value === "-h") result.help = true;
|
|
250
|
+
else if (value === "--host") result.host = argv[++index] || "";
|
|
251
|
+
else if (value === "--out") result.out = argv[++index] || "";
|
|
252
|
+
else fail(`未知参数:${value}`);
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function printHelp() {
|
|
258
|
+
console.log(`Build optional odai hook adapters.
|
|
259
|
+
|
|
260
|
+
Usage:
|
|
261
|
+
node skills/odai/scripts/build-hooks.mjs --host <host|all> --out <directory>
|
|
262
|
+
|
|
263
|
+
Hosts:
|
|
264
|
+
${supportedHosts.join(", ")}
|
|
265
|
+
|
|
266
|
+
The builder never installs hooks or edits a target project. Each generated
|
|
267
|
+
adapter is inactive until that project defines .odai/hooks.json.`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function fail(message) {
|
|
271
|
+
console.error(message);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|