agent-blocked 0.1.4 → 0.1.7
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/README.md +7 -5
- package/bin/agent-blocked.mjs +181 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,9 @@ Report a blocker manually:
|
|
|
16
16
|
npx agent-blocked@latest report --event=needs_direction --severity=medium --reason="Need human direction"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The installer adds a small `.agent-blocked/` directory plus tool-specific config or instruction files for the selected agent tools. Those files tell the agent when to escalate during goal mode, autonomous mode, background execution, hook-driven automation, CI-style runs, or another CLI-specific automated mode. They also provide a local reporter command that sends blocker events to your Agent Blocked alert profile.
|
|
19
|
+
The installer adds a small `.agent-blocked/` directory plus tool-specific config, hooks, or instruction files for the selected agent tools. Those files tell the agent when to escalate during goal mode, autonomous mode, background execution, hook-driven automation, CI-style runs, or another CLI-specific automated mode. They also provide a local reporter command that sends blocker events to your Agent Blocked alert profile.
|
|
20
|
+
|
|
21
|
+
For tools with native hooks, such as Claude Code and Codex, installing the adapter turns the hook on for that project. Make sure `AGENT_BLOCKED_AGENT_TOKEN` is available in the shell or secret store used by the agent.
|
|
20
22
|
|
|
21
23
|
Agents should not notify during normal interactive paired work when the human is already present and responding in the current conversation. In automated modes, agents should notify when continuing would waste time, create risk, or require a decision only the human can make:
|
|
22
24
|
|
|
@@ -27,12 +29,10 @@ Agents should not notify during normal interactive paired work when the human is
|
|
|
27
29
|
- `low_confidence` when the agent can continue but the chance of being wrong is high.
|
|
28
30
|
- `hard_blocked` when no productive path remains without human input.
|
|
29
31
|
|
|
30
|
-
Set
|
|
32
|
+
Set the scoped token in the shell or secret store where the agent runs:
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
|
-
export AGENT_BLOCKED_WEBHOOK_URL="https://agentblocked.com/api/agent-blocked"
|
|
34
35
|
export AGENT_BLOCKED_AGENT_TOKEN="your-scoped-token"
|
|
35
|
-
export AGENT_NAME="prod-agent"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Agents can report through the installed local reporter:
|
|
@@ -41,7 +41,9 @@ Agents can report through the installed local reporter:
|
|
|
41
41
|
node .agent-blocked/report.mjs --event=needs_credentials --severity=critical --reason="Missing deploy credentials"
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Codex
|
|
44
|
+
Codex hook reporting is installed by `npx agent-blocked@latest install --tool=codex`. Restart Codex or start a new Codex session after installation so the hook config is loaded.
|
|
45
|
+
|
|
46
|
+
Codex can also be run through the wrapper when you want hard process exits reported:
|
|
45
47
|
|
|
46
48
|
```bash
|
|
47
49
|
npx agent-blocked@latest codex -- codex "implement the requested change"
|
package/bin/agent-blocked.mjs
CHANGED
|
@@ -154,6 +154,119 @@ try {
|
|
|
154
154
|
}
|
|
155
155
|
`;
|
|
156
156
|
|
|
157
|
+
const codexHookScript = `#!/usr/bin/env node
|
|
158
|
+
|
|
159
|
+
let input = "";
|
|
160
|
+
process.stdin.setEncoding("utf8");
|
|
161
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
162
|
+
|
|
163
|
+
let event = {};
|
|
164
|
+
try {
|
|
165
|
+
event = input ? JSON.parse(input) : {};
|
|
166
|
+
} catch {
|
|
167
|
+
event = {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const webhookUrl = process.env.AGENT_BLOCKED_WEBHOOK_URL || "https://agentblocked.com/api/agent-blocked";
|
|
171
|
+
const token = process.env.AGENT_BLOCKED_AGENT_TOKEN;
|
|
172
|
+
const agentId = process.env.AGENT_NAME || "codex";
|
|
173
|
+
|
|
174
|
+
if (!token) process.exit(0);
|
|
175
|
+
|
|
176
|
+
function pick(...values) {
|
|
177
|
+
return values.find((value) => value !== undefined && value !== null && value !== "");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function hookEventName(payload) {
|
|
181
|
+
return pick(payload.hook_event_name, payload.hookEventName, payload.event_name, payload.eventName, payload.event, payload.type, "");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function toolName(payload) {
|
|
185
|
+
return pick(payload.tool_name, payload.toolName, payload.tool, payload.tool_call?.name, payload.toolCall?.name, "");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function exitCode(payload) {
|
|
189
|
+
return pick(
|
|
190
|
+
payload.exit_code,
|
|
191
|
+
payload.exitCode,
|
|
192
|
+
payload.result?.exit_code,
|
|
193
|
+
payload.result?.exitCode,
|
|
194
|
+
payload.tool_result?.exit_code,
|
|
195
|
+
payload.toolResult?.exitCode
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function failed(payload) {
|
|
200
|
+
if (payload.success === false || payload.ok === false) return true;
|
|
201
|
+
if (payload.error || payload.error_message || payload.errorMessage) return true;
|
|
202
|
+
const code = exitCode(payload);
|
|
203
|
+
return code !== undefined && code !== null && Number(code) !== 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function commandText(payload) {
|
|
207
|
+
return pick(
|
|
208
|
+
payload.command,
|
|
209
|
+
payload.cmd,
|
|
210
|
+
payload.tool_input?.cmd,
|
|
211
|
+
payload.toolInput?.cmd,
|
|
212
|
+
payload.tool_input?.command,
|
|
213
|
+
payload.toolInput?.command,
|
|
214
|
+
""
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const eventName = hookEventName(event);
|
|
219
|
+
const tool = toolName(event);
|
|
220
|
+
let payload = null;
|
|
221
|
+
|
|
222
|
+
if (eventName === "PermissionRequest" || /permission/i.test(eventName)) {
|
|
223
|
+
payload = {
|
|
224
|
+
eventType: "approval_required",
|
|
225
|
+
severity: "medium",
|
|
226
|
+
reason: \`Codex requested approval\${tool ? \` for \${tool}\` : ""}.\`
|
|
227
|
+
};
|
|
228
|
+
} else if ((eventName === "PostToolUse" || /post.*tool/i.test(eventName)) && failed(event)) {
|
|
229
|
+
const code = exitCode(event);
|
|
230
|
+
payload = {
|
|
231
|
+
eventType: "tool_failure",
|
|
232
|
+
severity: "medium",
|
|
233
|
+
reason: \`Codex tool failed\${tool ? \` during \${tool}\` : ""}\${code !== undefined ? \` with exit code \${code}\` : ""}.\`
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!payload) process.exit(0);
|
|
238
|
+
|
|
239
|
+
const details = {
|
|
240
|
+
hookEventName: eventName || "unknown",
|
|
241
|
+
toolName: tool || undefined,
|
|
242
|
+
command: commandText(event) || undefined,
|
|
243
|
+
cwd: event.cwd,
|
|
244
|
+
sessionId: event.session_id || event.sessionId,
|
|
245
|
+
threadId: event.thread_id || event.threadId,
|
|
246
|
+
exitCode: exitCode(event)
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
await fetch(webhookUrl, {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: {
|
|
253
|
+
authorization: \`Bearer \${token}\`,
|
|
254
|
+
"content-type": "application/json"
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
agentId,
|
|
258
|
+
...payload,
|
|
259
|
+
details: JSON.stringify(details),
|
|
260
|
+
provider: "openai",
|
|
261
|
+
model: "codex",
|
|
262
|
+
runId: details.sessionId || details.threadId
|
|
263
|
+
})
|
|
264
|
+
});
|
|
265
|
+
} catch {
|
|
266
|
+
// Hooks should not break Codex when notification delivery fails.
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
|
|
157
270
|
const instructions = `# Agent Blocked instructions for coding agents
|
|
158
271
|
|
|
159
272
|
Use Agent Blocked only when this agent is running without an actively engaged human in the loop, such as goal mode, autonomous mode, background execution, hook-driven automation, CI-style runs, or another CLI-specific automated mode.
|
|
@@ -173,11 +286,17 @@ node .agent-blocked/report.mjs --event=needs_direction --severity=medium --reaso
|
|
|
173
286
|
Environment expected by the report command:
|
|
174
287
|
|
|
175
288
|
\`\`\`bash
|
|
176
|
-
export AGENT_BLOCKED_WEBHOOK_URL="https://agentblocked.com/api/agent-blocked"
|
|
177
289
|
export AGENT_BLOCKED_AGENT_TOKEN="your-scoped-token"
|
|
178
|
-
export AGENT_NAME="your-agent-name"
|
|
179
290
|
\`\`\`
|
|
180
291
|
|
|
292
|
+
For Codex hook-based automatic reporting, install the Codex adapter and restart Codex or start a new Codex session so the hook config is loaded:
|
|
293
|
+
|
|
294
|
+
\`\`\`bash
|
|
295
|
+
npx agent-blocked@latest install --tool=codex
|
|
296
|
+
\`\`\`
|
|
297
|
+
|
|
298
|
+
The hook only reports matching hook events such as approval requests and failed tool calls.
|
|
299
|
+
|
|
181
300
|
Event types:
|
|
182
301
|
|
|
183
302
|
- hard_blocked: no productive path remains without human input.
|
|
@@ -252,6 +371,37 @@ function mergeClaudeSettings(path, settings) {
|
|
|
252
371
|
console.log(`Updated ${path} with Agent Blocked hooks.`);
|
|
253
372
|
}
|
|
254
373
|
|
|
374
|
+
function mergeCodexHooks(path, config) {
|
|
375
|
+
let currentConfig = {};
|
|
376
|
+
let currentHooks = {};
|
|
377
|
+
|
|
378
|
+
if (existsSync(path)) {
|
|
379
|
+
const existingRaw = readFileSync(path, "utf8");
|
|
380
|
+
try {
|
|
381
|
+
const parsed = JSON.parse(existingRaw);
|
|
382
|
+
currentConfig = parsed && typeof parsed === "object" ? parsed : {};
|
|
383
|
+
currentHooks = currentConfig.hooks && typeof currentConfig.hooks === "object" ? currentConfig.hooks : {};
|
|
384
|
+
} catch {
|
|
385
|
+
console.log(`Could not parse ${path}. Wrote example file for manual merge instead.`);
|
|
386
|
+
writeFileIfMissing(join(root, ".codex", "hooks.agent-blocked.example.json"), `${JSON.stringify(config, null, 2)}\n`);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const mergedHooks = { ...currentHooks };
|
|
392
|
+
for (const [eventName, hooks] of Object.entries(config.hooks || {})) {
|
|
393
|
+
mergedHooks[eventName] = mergeHookEntries(
|
|
394
|
+
Array.isArray(mergedHooks[eventName]) ? mergedHooks[eventName] : [],
|
|
395
|
+
hooks || []
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
currentConfig.hooks = mergedHooks;
|
|
400
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
401
|
+
writeFileSync(path, `${JSON.stringify(currentConfig, null, 2)}\n`);
|
|
402
|
+
console.log(`Updated ${path} with Agent Blocked hooks.`);
|
|
403
|
+
}
|
|
404
|
+
|
|
255
405
|
function upsertMarkedSection(path, title) {
|
|
256
406
|
const section = `${markerStart}
|
|
257
407
|
## ${title}
|
|
@@ -297,6 +447,26 @@ function installClaude() {
|
|
|
297
447
|
}
|
|
298
448
|
|
|
299
449
|
function installCodex() {
|
|
450
|
+
writeFileIfMissing(join(root, ".agent-blocked", "codex-hook.mjs"), codexHookScript);
|
|
451
|
+
const hooks = {
|
|
452
|
+
hooks: {
|
|
453
|
+
PermissionRequest: [
|
|
454
|
+
{
|
|
455
|
+
matcher: "*",
|
|
456
|
+
hooks: [{ type: "command", command: "node \"$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agent-blocked/codex-hook.mjs\"" }]
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
PostToolUse: [
|
|
460
|
+
{
|
|
461
|
+
matcher: "*",
|
|
462
|
+
hooks: [{ type: "command", command: "node \"$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agent-blocked/codex-hook.mjs\"" }]
|
|
463
|
+
}
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
writeFileIfMissing(join(root, ".codex", "hooks.agent-blocked.example.json"), `${JSON.stringify(hooks, null, 2)}\n`);
|
|
469
|
+
mergeCodexHooks(join(root, ".codex", "hooks.json"), hooks);
|
|
300
470
|
upsertMarkedSection(join(root, "AGENTS.md"), "Codex Agent Blocked setup");
|
|
301
471
|
}
|
|
302
472
|
|
|
@@ -329,7 +499,10 @@ function install() {
|
|
|
329
499
|
installers[name]();
|
|
330
500
|
}
|
|
331
501
|
|
|
332
|
-
console.log("\nNext: set
|
|
502
|
+
console.log("\nNext: set AGENT_BLOCKED_AGENT_TOKEN in the shell or secret store where your agent runs.");
|
|
503
|
+
if (selected.includes("codex")) {
|
|
504
|
+
console.log("Codex hook reporting is installed. Restart Codex or start a new Codex session if it was already running.");
|
|
505
|
+
}
|
|
333
506
|
console.log("Manual report command: node .agent-blocked/report.mjs --event=needs_direction --severity=medium --reason=\"Need human direction\"");
|
|
334
507
|
}
|
|
335
508
|
|
|
@@ -376,7 +549,11 @@ async function codex() {
|
|
|
376
549
|
const command = args[0] || "codex";
|
|
377
550
|
const commandArgs = args[0] ? args.slice(1) : [];
|
|
378
551
|
|
|
379
|
-
const child = spawn(command, commandArgs, {
|
|
552
|
+
const child = spawn(command, commandArgs, {
|
|
553
|
+
stdio: "inherit",
|
|
554
|
+
shell: false,
|
|
555
|
+
env: process.env
|
|
556
|
+
});
|
|
380
557
|
const code = await new Promise((resolve) => child.on("close", resolve));
|
|
381
558
|
|
|
382
559
|
if (code && process.env.AGENT_BLOCKED_AGENT_TOKEN) {
|