agent-blocked 0.1.5 → 0.1.9
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 +18 -2
- package/bin/agent-blocked.mjs +211 -1
- 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
|
|
|
@@ -39,7 +41,21 @@ Agents can report through the installed local reporter:
|
|
|
39
41
|
node .agent-blocked/report.mjs --event=needs_credentials --severity=critical --reason="Missing deploy credentials"
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
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
|
+
If you already had a Codex session open in that repo, exit the old session and choose the session to resume with:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx agent-blocked@latest restart --tool=codex
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To skip the picker and resume the most recent session:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx agent-blocked@latest restart --tool=codex --last
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Codex can also be run through the wrapper when you want hard process exits reported:
|
|
43
59
|
|
|
44
60
|
```bash
|
|
45
61
|
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.
|
|
@@ -176,6 +289,14 @@ Environment expected by the report command:
|
|
|
176
289
|
export AGENT_BLOCKED_AGENT_TOKEN="your-scoped-token"
|
|
177
290
|
\`\`\`
|
|
178
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
|
+
|
|
179
300
|
Event types:
|
|
180
301
|
|
|
181
302
|
- hard_blocked: no productive path remains without human input.
|
|
@@ -250,6 +371,37 @@ function mergeClaudeSettings(path, settings) {
|
|
|
250
371
|
console.log(`Updated ${path} with Agent Blocked hooks.`);
|
|
251
372
|
}
|
|
252
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
|
+
|
|
253
405
|
function upsertMarkedSection(path, title) {
|
|
254
406
|
const section = `${markerStart}
|
|
255
407
|
## ${title}
|
|
@@ -295,6 +447,26 @@ function installClaude() {
|
|
|
295
447
|
}
|
|
296
448
|
|
|
297
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);
|
|
298
470
|
upsertMarkedSection(join(root, "AGENTS.md"), "Codex Agent Blocked setup");
|
|
299
471
|
}
|
|
300
472
|
|
|
@@ -328,6 +500,10 @@ function install() {
|
|
|
328
500
|
}
|
|
329
501
|
|
|
330
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
|
+
console.log("To choose an existing Codex session with hooks loaded, run: npx agent-blocked@latest restart --tool=codex");
|
|
506
|
+
}
|
|
331
507
|
console.log("Manual report command: node .agent-blocked/report.mjs --event=needs_direction --severity=medium --reason=\"Need human direction\"");
|
|
332
508
|
}
|
|
333
509
|
|
|
@@ -374,7 +550,11 @@ async function codex() {
|
|
|
374
550
|
const command = args[0] || "codex";
|
|
375
551
|
const commandArgs = args[0] ? args.slice(1) : [];
|
|
376
552
|
|
|
377
|
-
const child = spawn(command, commandArgs, {
|
|
553
|
+
const child = spawn(command, commandArgs, {
|
|
554
|
+
stdio: "inherit",
|
|
555
|
+
shell: false,
|
|
556
|
+
env: process.env
|
|
557
|
+
});
|
|
378
558
|
const code = await new Promise((resolve) => child.on("close", resolve));
|
|
379
559
|
|
|
380
560
|
if (code && process.env.AGENT_BLOCKED_AGENT_TOKEN) {
|
|
@@ -393,6 +573,30 @@ async function codex() {
|
|
|
393
573
|
process.exit(code || 0);
|
|
394
574
|
}
|
|
395
575
|
|
|
576
|
+
async function restart() {
|
|
577
|
+
const tool = arg("tool", "codex").toLowerCase();
|
|
578
|
+
if (tool !== "codex") {
|
|
579
|
+
console.error("Restart helper currently supports --tool=codex.");
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const resumeArgs = hasFlag("last") ? ["resume", "--last"] : ["resume"];
|
|
584
|
+
console.log(
|
|
585
|
+
hasFlag("last")
|
|
586
|
+
? "Starting a fresh Codex process with the most recent session. Exit the old Codex session first if it is still open."
|
|
587
|
+
: "Starting a fresh Codex process with the session picker. Exit the old Codex session first if it is still open."
|
|
588
|
+
);
|
|
589
|
+
process.argv = [
|
|
590
|
+
process.argv[0],
|
|
591
|
+
process.argv[1],
|
|
592
|
+
"codex",
|
|
593
|
+
"--",
|
|
594
|
+
"codex",
|
|
595
|
+
...resumeArgs
|
|
596
|
+
];
|
|
597
|
+
await codex();
|
|
598
|
+
}
|
|
599
|
+
|
|
396
600
|
function help() {
|
|
397
601
|
console.log(`Agent Blocked CLI
|
|
398
602
|
|
|
@@ -400,9 +604,12 @@ Usage:
|
|
|
400
604
|
agent-blocked install --tool=claude|codex|gemini|aider|all
|
|
401
605
|
agent-blocked report --event=needs_direction --severity=medium --reason="Need human help"
|
|
402
606
|
agent-blocked codex -- codex "your task"
|
|
607
|
+
agent-blocked restart --tool=codex [--last]
|
|
403
608
|
|
|
404
609
|
Examples:
|
|
405
610
|
npx agent-blocked@latest install --tool=all
|
|
611
|
+
npx agent-blocked@latest restart --tool=codex
|
|
612
|
+
npx agent-blocked@latest restart --tool=codex --last
|
|
406
613
|
npx agent-blocked@latest report --event=needs_credentials --severity=critical --reason="Missing AWS credentials"
|
|
407
614
|
`);
|
|
408
615
|
}
|
|
@@ -417,6 +624,9 @@ switch (commandName()) {
|
|
|
417
624
|
case "codex":
|
|
418
625
|
await codex();
|
|
419
626
|
break;
|
|
627
|
+
case "restart":
|
|
628
|
+
await restart();
|
|
629
|
+
break;
|
|
420
630
|
default:
|
|
421
631
|
help();
|
|
422
632
|
}
|