@tangle-network/agent-profile-materialize 0.15.2 → 0.16.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/dist/candidate-types.d.ts +32 -0
- package/dist/candidate-types.js +1 -0
- package/dist/candidate.d.ts +9 -0
- package/dist/candidate.js +279 -0
- package/dist/commands.d.ts +2 -0
- package/dist/commands.js +32 -0
- package/dist/control-maps.d.ts +34 -0
- package/dist/control-maps.js +185 -0
- package/dist/execution-controls.d.ts +5 -0
- package/dist/execution-controls.js +186 -0
- package/dist/harness.d.ts +81 -0
- package/dist/harness.js +167 -0
- package/dist/hooks.d.ts +6 -0
- package/dist/hooks.js +261 -0
- package/dist/index.d.ts +23 -623
- package/dist/index.js +14 -3733
- package/dist/mcp.d.ts +22 -0
- package/dist/mcp.js +244 -0
- package/dist/plan-application.d.ts +10 -0
- package/dist/plan-application.js +152 -0
- package/dist/plan-digest.d.ts +18 -0
- package/dist/plan-digest.js +90 -0
- package/dist/plan-json.d.ts +18 -0
- package/dist/plan-json.js +91 -0
- package/dist/plan-normalize.d.ts +13 -0
- package/dist/plan-normalize.js +156 -0
- package/dist/plan-prompt.d.ts +38 -0
- package/dist/plan-prompt.js +49 -0
- package/dist/plan-support.d.ts +9 -0
- package/dist/plan-support.js +22 -0
- package/dist/plan-types.d.ts +294 -0
- package/dist/plan-types.js +106 -0
- package/dist/prime-agent-dir.d.ts +47 -0
- package/dist/prime-agent-dir.js +164 -0
- package/dist/prime-profile.d.ts +88 -0
- package/dist/prime-profile.js +284 -0
- package/dist/profile-materializer.d.ts +12 -0
- package/dist/profile-materializer.js +59 -0
- package/dist/profile-plan.d.ts +14 -0
- package/dist/profile-plan.js +86 -0
- package/dist/profile-prompt.d.ts +4 -0
- package/dist/profile-prompt.js +105 -0
- package/dist/profile-support.d.ts +8 -0
- package/dist/profile-support.js +6 -0
- package/dist/resource-lowering.d.ts +7 -0
- package/dist/resource-lowering.js +103 -0
- package/dist/resource-resolution.d.ts +27 -0
- package/dist/resource-resolution.js +241 -0
- package/dist/subagents.d.ts +6 -0
- package/dist/subagents.js +133 -0
- package/dist/workspace-apply.d.ts +12 -0
- package/dist/workspace-apply.js +334 -0
- package/dist/workspace-files.d.ts +8 -0
- package/dist/workspace-files.js +46 -0
- package/package.json +3 -3
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export function materializeHooks(h, hooks, add, unsupported, plan) {
|
|
2
|
+
switch (h) {
|
|
3
|
+
case "claude-code": {
|
|
4
|
+
// Claude settings hooks{} — each event → [{matcher, hooks:[{type:command,command}]}].
|
|
5
|
+
const settingsHooks = {};
|
|
6
|
+
for (const [event, cmds] of Object.entries(hooks)) {
|
|
7
|
+
if (!hookEventSupported("claude-code", event)) {
|
|
8
|
+
unsupported("hooks", `claude-code: unsupported hook event "${event}"`);
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
settingsHooks[event] = cmds.map((c) => ({
|
|
12
|
+
matcher: c.matcher ?? "*",
|
|
13
|
+
hooks: [
|
|
14
|
+
{
|
|
15
|
+
type: "command",
|
|
16
|
+
command: materializedHookCommand(event, c, add, plan),
|
|
17
|
+
...(c.timeoutMs ? { timeout: timeoutSeconds(c.timeoutMs) } : {}),
|
|
18
|
+
...(c.blocking === false ? { async: true } : {}),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
add(CLAUDE_SETTINGS_PATH, serializeWorkspacePlanJson({ hooks: settingsHooks }));
|
|
24
|
+
registerClaudeSettings(plan);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
case "nanoclaw":
|
|
28
|
+
unsupported("hooks", "nanoclaw's chat bridge does not expose Claude Code hook settings to the spawned agent");
|
|
29
|
+
break;
|
|
30
|
+
case "gemini": {
|
|
31
|
+
const settingsHooks = {};
|
|
32
|
+
for (const [event, cmds] of Object.entries(hooks)) {
|
|
33
|
+
if (!hookEventSupported("gemini", event)) {
|
|
34
|
+
unsupported("hooks", `gemini: unsupported hook event "${event}"`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (cmds.some((command) => command.blocking === false)) {
|
|
38
|
+
unsupported("hooks", `gemini: hook event "${event}" does not support non-blocking commands`);
|
|
39
|
+
}
|
|
40
|
+
settingsHooks[event] = cmds.map((c) => ({
|
|
41
|
+
matcher: c.matcher ?? "*",
|
|
42
|
+
hooks: [
|
|
43
|
+
{
|
|
44
|
+
type: "command",
|
|
45
|
+
command: materializedHookCommand(event, c, add, plan),
|
|
46
|
+
...(c.timeoutMs ? { timeout: c.timeoutMs } : {}),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
add(".gemini/settings.json", serializeWorkspacePlanJson({ hooks: settingsHooks }));
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "codex": {
|
|
55
|
+
for (const [event, cmds] of Object.entries(hooks).sort(([left], [right]) => compareStrings(left, right))) {
|
|
56
|
+
if (!hookEventSupported("codex", event)) {
|
|
57
|
+
unsupported("hooks", `codex: unsupported hook event "${event}"`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (cmds.some((command) => command.blocking === false)) {
|
|
61
|
+
unsupported("hooks", `codex: hook event "${event}" cannot execute asynchronous commands`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const entries = cmds.map((command) => {
|
|
65
|
+
const handler = [
|
|
66
|
+
`type = ${tomlStr("command")}`,
|
|
67
|
+
`command = ${tomlStr(materializedHookCommand(event, command, add, plan))}`,
|
|
68
|
+
...(command.timeoutMs
|
|
69
|
+
? [`timeout = ${timeoutSeconds(command.timeoutMs)}`]
|
|
70
|
+
: []),
|
|
71
|
+
];
|
|
72
|
+
return `{ matcher = ${tomlStr(command.matcher ?? "*")}, hooks = [{ ${handler.join(", ")} }] }`;
|
|
73
|
+
});
|
|
74
|
+
plan.flags.push("-c", `hooks.${event}=[${entries.join(", ")}]`);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "opencode":
|
|
79
|
+
unsupported("hooks", "opencode hooks require executable JS/TS plugins; command hooks cannot be translated exactly");
|
|
80
|
+
break;
|
|
81
|
+
case "kimi-code":
|
|
82
|
+
unsupported("hooks", "kimi hooks are [[hooks]] in the config TOML loaded via --config-file, not cwd — pass --config-file");
|
|
83
|
+
break;
|
|
84
|
+
case "pi":
|
|
85
|
+
unsupported("hooks", "pi hooks are TS extensions (.pi/extensions/*.ts) — build an extension");
|
|
86
|
+
break;
|
|
87
|
+
case "prime":
|
|
88
|
+
unsupported("hooks", "prime hooks are TS extensions (fork core/extensions), not command hooks — build an extension");
|
|
89
|
+
break;
|
|
90
|
+
case "hermes":
|
|
91
|
+
unsupported("hooks", "hermes project plugins do not expose an exact command-hook mapping");
|
|
92
|
+
break;
|
|
93
|
+
case "openclaw":
|
|
94
|
+
unsupported("hooks", "openclaw hooks are DISABLED by default — enable in ~/.openclaw/openclaw.json; cwd hooks/ alone is a no-op");
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function registerClaudeSettings(plan) {
|
|
99
|
+
for (let index = 0; index < plan.flags.length - 1; index += 1) {
|
|
100
|
+
if (plan.flags[index] === "--settings" &&
|
|
101
|
+
plan.flags[index + 1] === CLAUDE_SETTINGS_PATH) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
plan.flags.push("--settings", CLAUDE_SETTINGS_PATH);
|
|
106
|
+
}
|
|
107
|
+
const CLAUDE_HOOK_EVENTS = new Set([
|
|
108
|
+
"ConfigChange",
|
|
109
|
+
"CwdChanged",
|
|
110
|
+
"Elicitation",
|
|
111
|
+
"ElicitationResult",
|
|
112
|
+
"FileChanged",
|
|
113
|
+
"InstructionsLoaded",
|
|
114
|
+
"MessageDisplay",
|
|
115
|
+
"Notification",
|
|
116
|
+
"PermissionDenied",
|
|
117
|
+
"PermissionRequest",
|
|
118
|
+
"PostCompact",
|
|
119
|
+
"PostToolBatch",
|
|
120
|
+
"PostToolUse",
|
|
121
|
+
"PostToolUseFailure",
|
|
122
|
+
"PreCompact",
|
|
123
|
+
"PreToolUse",
|
|
124
|
+
"SessionEnd",
|
|
125
|
+
"SessionStart",
|
|
126
|
+
"Setup",
|
|
127
|
+
"Stop",
|
|
128
|
+
"StopFailure",
|
|
129
|
+
"SubagentStart",
|
|
130
|
+
"SubagentStop",
|
|
131
|
+
"TaskCompleted",
|
|
132
|
+
"TaskCreated",
|
|
133
|
+
"TeammateIdle",
|
|
134
|
+
"UserPromptExpansion",
|
|
135
|
+
"UserPromptSubmit",
|
|
136
|
+
"WorktreeCreate",
|
|
137
|
+
"WorktreeRemove",
|
|
138
|
+
]);
|
|
139
|
+
const CODEX_HOOK_EVENTS = new Set([
|
|
140
|
+
"PermissionRequest",
|
|
141
|
+
"PostCompact",
|
|
142
|
+
"PostToolUse",
|
|
143
|
+
"PreCompact",
|
|
144
|
+
"PreToolUse",
|
|
145
|
+
"SessionStart",
|
|
146
|
+
"Stop",
|
|
147
|
+
"SubagentStart",
|
|
148
|
+
"SubagentStop",
|
|
149
|
+
"UserPromptSubmit",
|
|
150
|
+
]);
|
|
151
|
+
const GEMINI_HOOK_EVENTS = new Set([
|
|
152
|
+
"AfterAgent",
|
|
153
|
+
"AfterModel",
|
|
154
|
+
"AfterTool",
|
|
155
|
+
"BeforeAgent",
|
|
156
|
+
"BeforeModel",
|
|
157
|
+
"BeforeTool",
|
|
158
|
+
"BeforeToolSelection",
|
|
159
|
+
"Notification",
|
|
160
|
+
"PreCompress",
|
|
161
|
+
"SessionEnd",
|
|
162
|
+
"SessionStart",
|
|
163
|
+
]);
|
|
164
|
+
function hookEventSupported(harness, event) {
|
|
165
|
+
switch (harness) {
|
|
166
|
+
case "claude-code":
|
|
167
|
+
return CLAUDE_HOOK_EVENTS.has(event);
|
|
168
|
+
case "codex":
|
|
169
|
+
return CODEX_HOOK_EVENTS.has(event);
|
|
170
|
+
case "gemini":
|
|
171
|
+
return GEMINI_HOOK_EVENTS.has(event);
|
|
172
|
+
default:
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function timeoutSeconds(timeoutMs) {
|
|
177
|
+
return Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
178
|
+
}
|
|
179
|
+
function materializedHookCommand(event, command, add, plan) {
|
|
180
|
+
if (!command.env || Object.keys(command.env).length === 0) {
|
|
181
|
+
return command.command;
|
|
182
|
+
}
|
|
183
|
+
const wrapperId = canonicalCandidateDigest({
|
|
184
|
+
event,
|
|
185
|
+
command: command.command,
|
|
186
|
+
env: Object.fromEntries(Object.entries(command.env).sort(([left], [right]) => compareStrings(left, right))),
|
|
187
|
+
}).slice("sha256:".length);
|
|
188
|
+
const path = `.tangle/profile-hooks/${wrapperId}.mjs`;
|
|
189
|
+
const envSources = Object.fromEntries(Object.entries(command.env)
|
|
190
|
+
.sort(([left], [right]) => compareStrings(left, right))
|
|
191
|
+
.map(([name, value]) => {
|
|
192
|
+
const sourceName = `TANGLE_PROFILE_HOOK_${sha256(`${wrapperId}\0${name}`)
|
|
193
|
+
.slice(0, 24)
|
|
194
|
+
.toUpperCase()}`;
|
|
195
|
+
setPlanEnvironment(plan, sourceName, value);
|
|
196
|
+
return [name, sourceName];
|
|
197
|
+
}));
|
|
198
|
+
add(path, hookWrapperSourceFromEnvironment("/bin/sh", ["-c", command.command], envSources));
|
|
199
|
+
return `node ${path}`;
|
|
200
|
+
}
|
|
201
|
+
function hookWrapperSourceFromEnvironment(executable, args, envSources) {
|
|
202
|
+
return [
|
|
203
|
+
'import { spawnSync } from "node:child_process"',
|
|
204
|
+
`const executable = ${JSON.stringify(executable)}`,
|
|
205
|
+
`const args = ${JSON.stringify(args)}`,
|
|
206
|
+
`const envSources = ${JSON.stringify(envSources)}`,
|
|
207
|
+
"const extraEnv = {}",
|
|
208
|
+
"for (const [name, source] of Object.entries(envSources)) {",
|
|
209
|
+
" const value = process.env[source]",
|
|
210
|
+
" if (value === undefined) {",
|
|
211
|
+
' console.error("missing materialized hook environment " + source)',
|
|
212
|
+
" process.exit(127)",
|
|
213
|
+
" }",
|
|
214
|
+
" extraEnv[name] = value",
|
|
215
|
+
"}",
|
|
216
|
+
"const result = spawnSync(executable, args, {",
|
|
217
|
+
' stdio: "inherit",',
|
|
218
|
+
" env: { ...process.env, ...extraEnv },",
|
|
219
|
+
" shell: false,",
|
|
220
|
+
"})",
|
|
221
|
+
"if (result.error) {",
|
|
222
|
+
" console.error(result.error.message)",
|
|
223
|
+
" process.exit(127)",
|
|
224
|
+
"}",
|
|
225
|
+
"if (result.signal) {",
|
|
226
|
+
' console.error("hook terminated by " + result.signal)',
|
|
227
|
+
" process.exit(1)",
|
|
228
|
+
"}",
|
|
229
|
+
"process.exit(result.status ?? 1)",
|
|
230
|
+
"",
|
|
231
|
+
].join("\n");
|
|
232
|
+
}
|
|
233
|
+
export function hookWrapperSource(executable, args, env) {
|
|
234
|
+
return [
|
|
235
|
+
'import { spawnSync } from "node:child_process"',
|
|
236
|
+
`const executable = ${JSON.stringify(executable)}`,
|
|
237
|
+
`const args = ${JSON.stringify(args)}`,
|
|
238
|
+
`const extraEnv = ${JSON.stringify(env)}`,
|
|
239
|
+
"const result = spawnSync(executable, args, {",
|
|
240
|
+
' stdio: "inherit",',
|
|
241
|
+
" env: { ...process.env, ...extraEnv },",
|
|
242
|
+
" shell: false,",
|
|
243
|
+
"})",
|
|
244
|
+
"if (result.error) {",
|
|
245
|
+
" console.error(result.error.message)",
|
|
246
|
+
" process.exit(127)",
|
|
247
|
+
"}",
|
|
248
|
+
"if (result.signal) {",
|
|
249
|
+
' console.error("hook terminated by " + result.signal)',
|
|
250
|
+
" process.exit(1)",
|
|
251
|
+
"}",
|
|
252
|
+
"process.exit(result.status ?? 1)",
|
|
253
|
+
"",
|
|
254
|
+
].join("\n");
|
|
255
|
+
}
|
|
256
|
+
import { canonicalCandidateDigest } from "@tangle-network/agent-interface";
|
|
257
|
+
import { CLAUDE_SETTINGS_PATH } from "./harness.js";
|
|
258
|
+
import { sha256 } from "./plan-digest.js";
|
|
259
|
+
import { serializeWorkspacePlanJson, tomlStr } from "./plan-json.js";
|
|
260
|
+
import { setPlanEnvironment } from "./profile-plan.js";
|
|
261
|
+
import { compareStrings } from "./workspace-files.js";
|