@agent-beam/beam 0.2.37 → 0.2.39
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 -13
- package/dist/access-risk.d.ts +10 -0
- package/dist/access-risk.js +43 -0
- package/dist/access-risk.js.map +1 -0
- package/dist/action-context.d.ts +30 -0
- package/dist/action-context.js +203 -0
- package/dist/action-context.js.map +1 -0
- package/dist/activity.d.ts +22 -0
- package/dist/activity.js +132 -0
- package/dist/activity.js.map +1 -0
- package/dist/agent-hook-install.d.ts +8 -0
- package/dist/agent-hook-install.js +278 -0
- package/dist/agent-hook-install.js.map +1 -0
- package/dist/agent-installers.js +16 -3
- package/dist/agent-installers.js.map +1 -1
- package/dist/agent-plugins.d.ts +3 -0
- package/dist/agent-plugins.js +66 -0
- package/dist/agent-plugins.js.map +1 -0
- package/dist/agents.d.ts +1 -0
- package/dist/agents.js +30 -184
- package/dist/agents.js.map +1 -1
- package/dist/behavioral-anomaly.d.ts +16 -0
- package/dist/behavioral-anomaly.js +66 -0
- package/dist/behavioral-anomaly.js.map +1 -0
- package/dist/cli.js +28 -10
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +203 -100
- package/dist/client.js.map +1 -1
- package/dist/connect.d.ts +7 -0
- package/dist/connect.js +12 -0
- package/dist/connect.js.map +1 -1
- package/dist/core.d.ts +50 -2
- package/dist/core.js +73 -24
- package/dist/core.js.map +1 -1
- package/dist/credential-egress.d.ts +17 -0
- package/dist/credential-egress.js +325 -0
- package/dist/credential-egress.js.map +1 -0
- package/dist/custom-rules.js +2 -1
- package/dist/custom-rules.js.map +1 -1
- package/dist/data-detectors.js +60 -4
- package/dist/data-detectors.js.map +1 -1
- package/dist/destructive-risk.d.ts +1 -0
- package/dist/destructive-risk.js +70 -0
- package/dist/destructive-risk.js.map +1 -0
- package/dist/edit-stats.js +5 -5
- package/dist/edit-stats.js.map +1 -1
- package/dist/entity-redaction.d.ts +11 -0
- package/dist/entity-redaction.js +88 -0
- package/dist/entity-redaction.js.map +1 -0
- package/dist/file-lock.d.ts +1 -0
- package/dist/file-lock.js +54 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/forward.d.ts +13 -6
- package/dist/forward.js +120 -42
- package/dist/forward.js.map +1 -1
- package/dist/hook-adapters.d.ts +2 -2
- package/dist/hook-adapters.js +59 -60
- package/dist/hook-adapters.js.map +1 -1
- package/dist/hook-catalog.d.ts +12 -0
- package/dist/hook-catalog.js +469 -0
- package/dist/hook-catalog.js.map +1 -0
- package/dist/hook-config.d.ts +8 -0
- package/dist/hook-config.js +80 -0
- package/dist/hook-config.js.map +1 -0
- package/dist/hook-spool.d.ts +3 -0
- package/dist/hook-spool.js +56 -0
- package/dist/hook-spool.js.map +1 -0
- package/dist/install.d.ts +5 -5
- package/dist/install.js +48 -199
- package/dist/install.js.map +1 -1
- package/dist/inventory.js +1 -1
- package/dist/inventory.js.map +1 -1
- package/dist/jev.js +2 -1
- package/dist/jev.js.map +1 -1
- package/dist/judge.js +2 -1
- package/dist/judge.js.map +1 -1
- package/dist/mitigation-pattern.d.ts +1 -0
- package/dist/mitigation-pattern.js +140 -0
- package/dist/mitigation-pattern.js.map +1 -0
- package/dist/package-risk-rules.js +2 -2
- package/dist/package-risk-rules.js.map +1 -1
- package/dist/policy-paths.d.ts +20 -0
- package/dist/policy-paths.js +145 -0
- package/dist/policy-paths.js.map +1 -0
- package/dist/policy.d.ts +5 -0
- package/dist/policy.js +78 -10
- package/dist/policy.js.map +1 -1
- package/dist/rule-catalog.d.ts +3 -1
- package/dist/rule-catalog.js +124 -24
- package/dist/rule-catalog.js.map +1 -1
- package/dist/rule-matches.d.ts +13 -0
- package/dist/rule-matches.js +42 -0
- package/dist/rule-matches.js.map +1 -0
- package/dist/serve.js +55 -5
- package/dist/serve.js.map +1 -1
- package/dist/server.js +7 -6
- package/dist/server.js.map +1 -1
- package/dist/setup.js +26 -13
- package/dist/setup.js.map +1 -1
- package/dist/skill-risk-rules.js +2 -2
- package/dist/skill-risk-rules.js.map +1 -1
- package/dist/skill-setup.js +4 -5
- package/dist/skill-setup.js.map +1 -1
- package/dist/skill-sync.d.ts +5 -1
- package/dist/skill-sync.js +115 -14
- package/dist/skill-sync.js.map +1 -1
- package/dist/store.js +6 -9
- package/dist/store.js.map +1 -1
- package/dist/studio.js +2 -2
- package/package.json +6 -3
- package/stress-report.json +48 -48
package/dist/agents.js
CHANGED
|
@@ -1,188 +1,34 @@
|
|
|
1
|
+
import { HOOK_CATALOG, hookKind } from "./hook-catalog.js";
|
|
2
|
+
import { mergeHookSpec, unmergeHookSpec } from "./hook-config.js";
|
|
1
3
|
import { join } from "node:path";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return { version: existing.version ?? 1, ...existing, hooks: { ...hooks, [event]: list } };
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
// Copilot CLI's dedicated hook file uses `{ type: "command", bash, timeoutSec }` entries.
|
|
34
|
-
function mergeCopilotHooks(event) {
|
|
35
|
-
return (existing, command) => {
|
|
36
|
-
const hooks = obj(existing.hooks);
|
|
37
|
-
const list = arr(hooks[event]).map(obj);
|
|
38
|
-
if (!list.some(entry => entry.bash === command))
|
|
39
|
-
list.push({ type: "command", bash: command, timeoutSec: 30 });
|
|
40
|
-
return { version: existing.version ?? 1, ...existing, hooks: { ...hooks, [event]: list } };
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
// Reverse of mergeClaudeStyleHooks: drops beam's command out of each entry's inner hooks array
|
|
44
|
-
// (rather than the whole entry), so a user-added hook sharing the same matcher is left alone.
|
|
45
|
-
function unmergeClaudeStyleHooks(event) {
|
|
46
|
-
return (existing, isOwnCommand) => {
|
|
47
|
-
const hooks = obj(existing.hooks);
|
|
48
|
-
if (!(event in hooks))
|
|
49
|
-
return existing;
|
|
50
|
-
const list = arr(hooks[event]).map(obj)
|
|
51
|
-
.map(entry => ({ ...entry, hooks: arr(entry.hooks).map(obj).filter(h => !isOwnCommand(String(h.command ?? ""))) }))
|
|
52
|
-
.filter(entry => arr(entry.hooks).length > 0);
|
|
53
|
-
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// Reverse of mergeCursorHooks.
|
|
57
|
-
function unmergeCursorHooks(event) {
|
|
58
|
-
return (existing, isOwnCommand) => {
|
|
59
|
-
const hooks = obj(existing.hooks);
|
|
60
|
-
if (!(event in hooks))
|
|
61
|
-
return existing;
|
|
62
|
-
const list = arr(hooks[event]).map(obj).filter(entry => !isOwnCommand(String(entry.command ?? "")));
|
|
63
|
-
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
// Reverse of mergeCopilotHooks.
|
|
67
|
-
function unmergeCopilotHooks(event) {
|
|
68
|
-
return (existing, isOwnCommand) => {
|
|
69
|
-
const hooks = obj(existing.hooks);
|
|
70
|
-
if (!(event in hooks))
|
|
71
|
-
return existing;
|
|
72
|
-
const list = arr(hooks[event]).map(obj).filter(entry => !isOwnCommand(String(entry.bash ?? "")));
|
|
73
|
-
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
// Installs beam into more than one hook event (e.g. the tool-call gate *and* the prompt-submit
|
|
77
|
-
// gate) by folding each single-event merger over the config in turn. A disabled agent only ever
|
|
78
|
-
// reliably stops at the pre-action gate if that agent never calls a tool in a turn (pure chat) --
|
|
79
|
-
// registering the prompt-submit event too means a disabled agent's policy.disabledAgents block
|
|
80
|
-
// (see policy.ts's evaluate()) fires before the prompt is even processed, not just before a tool
|
|
81
|
-
// call that may never come.
|
|
82
|
-
function mergeAll(mergers) {
|
|
83
|
-
return (existing, command) => mergers.reduce((acc, merge) => merge(acc, command), existing);
|
|
84
|
-
}
|
|
85
|
-
function unmergeAll(unmergers) {
|
|
86
|
-
return (existing, isOwnCommand) => unmergers.reduce((acc, unmerge) => unmerge(acc, isOwnCommand), existing);
|
|
87
|
-
}
|
|
88
|
-
export const AGENTS = [
|
|
89
|
-
{
|
|
90
|
-
id: "claude-code", name: "Claude Code",
|
|
91
|
-
configs: [".claude/settings.json"], artifacts: [".claude/projects"],
|
|
92
|
-
hookConfigPath: ".claude/settings.json", hookEventName: "PreToolUse",
|
|
93
|
-
mergeHookConfig: mergeAll([mergeClaudeStyleHooks("PreToolUse"), mergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
94
|
-
unmergeHookConfig: unmergeAll([unmergeClaudeStyleHooks("PreToolUse"), unmergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
95
|
-
adapter: "passthrough", verifiedPayload: true,
|
|
96
|
-
notes: "Payload verified against code.claude.com/docs/en/hooks. Also installs UserPromptSubmit (same hookSpecificOutput.permissionDecision contract, just hookEventName: \"UserPromptSubmit\") so a disabled agent is stopped before the prompt is processed, not only at its first tool call."
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
id: "codex", name: "Codex",
|
|
100
|
-
configs: [".codex/config.toml"], artifacts: [".codex/sessions", ".codex/archived_sessions"],
|
|
101
|
-
hookConfigPath: ".codex/hooks.json", hookEventName: "PreToolUse",
|
|
102
|
-
mergeHookConfig: mergeAll([mergeClaudeStyleHooks("PreToolUse"), mergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
103
|
-
unmergeHookConfig: unmergeAll([unmergeClaudeStyleHooks("PreToolUse"), unmergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
104
|
-
adapter: "passthrough", verifiedPayload: true,
|
|
105
|
-
notes: "Same hooks.json shape and stdin payload fields as Claude Code (session_id, cwd, hook_event_name, tool_name, tool_input, tool_use_id). Also installs UserPromptSubmit for the same disabled-agent-at-prompt-time reason."
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
id: "cursor", name: "Cursor",
|
|
109
|
-
// ".cursor/hooks.json" is the file beam itself writes on install, so it can never be used to
|
|
110
|
-
// detect a pre-existing Cursor install (circular). The bare ".cursor" directory is what
|
|
111
|
-
// Cursor itself creates just from being used, so it's a real presence signal.
|
|
112
|
-
configs: [".cursor"], artifacts: [".cursor/projects"],
|
|
113
|
-
hookConfigPath: ".cursor/hooks.json", hookEventName: "preToolUse",
|
|
114
|
-
mergeHookConfig: mergeAll([mergeCursorHooks("preToolUse"), mergeCursorHooks("beforeSubmitPrompt")]),
|
|
115
|
-
unmergeHookConfig: unmergeAll([unmergeCursorHooks("preToolUse"), unmergeCursorHooks("beforeSubmitPrompt")]),
|
|
116
|
-
adapter: "passthrough", verifiedPayload: true,
|
|
117
|
-
notes: "Payload verified against cursor.com/docs/hooks; extra fields (model, conversation_id, ...) are ignored, not misread. Also installs beforeSubmitPrompt -- unlike preToolUse's permission field, that event's block contract is {continue: false, user_message} (see client.ts emitDeny)."
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
id: "copilot-cli", name: "GitHub Copilot CLI",
|
|
121
|
-
// Same circularity as Cursor above: ".copilot/hooks/beam.json" is our own output.
|
|
122
|
-
// ".copilot/config.json" is what the real Copilot CLI writes on its own first run.
|
|
123
|
-
configs: [".copilot/config.json"], artifacts: [".copilot/session-state"],
|
|
124
|
-
hookConfigPath: ".copilot/hooks/beam.json", hookEventName: "preToolUse",
|
|
125
|
-
mergeHookConfig: mergeAll([mergeCopilotHooks("preToolUse"), mergeCopilotHooks("userPromptSubmitted")]),
|
|
126
|
-
unmergeHookConfig: unmergeAll([unmergeCopilotHooks("preToolUse"), unmergeCopilotHooks("userPromptSubmitted")]),
|
|
127
|
-
adapter: "copilot-camel", verifiedPayload: true,
|
|
128
|
-
notes: "Payload verified against docs.github.com Copilot hooks reference (camelCase: sessionId, cwd, toolName, toolArgs). userPromptSubmitted is also installed for observation/audit only -- GitHub's docs state its stdout is ignored (no blocking contract), so a disabled agent is only actually stopped at its first preToolUse call."
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "gemini", name: "Gemini CLI",
|
|
132
|
-
// ".gemini/settings.json" is our own write target (circular, like Cursor/Copilot above); the
|
|
133
|
-
// bare ".gemini" directory (created by any use of the Gemini CLI, e.g. mcp_config.json,
|
|
134
|
-
// projects/) is the reliable presence signal instead.
|
|
135
|
-
configs: [".gemini"], artifacts: [".gemini/tmp"],
|
|
136
|
-
hookConfigPath: ".gemini/settings.json", hookEventName: "BeforeTool",
|
|
137
|
-
// BeforeTool/BeforeAgent use the exact same {matcher, hooks: [{type, command}]} array shape
|
|
138
|
-
// as Claude Code's PreToolUse/UserPromptSubmit (geminicli.com/docs/hooks/reference) --
|
|
139
|
-
// beam previously targeted ".agents/hooks.json" with a "pre_tool_execution" event and a
|
|
140
|
-
// "beam-security" wrapper, which is actually the unrelated Gemini API "Managed Agents"
|
|
141
|
-
// sandbox hook contract (ai.google.dev/gemini-api/docs/agent-hooks), not Gemini CLI's own.
|
|
142
|
-
mergeHookConfig: mergeAll([mergeClaudeStyleHooks("BeforeTool"), mergeClaudeStyleHooks("BeforeAgent")]),
|
|
143
|
-
unmergeHookConfig: unmergeAll([unmergeClaudeStyleHooks("BeforeTool"), unmergeClaudeStyleHooks("BeforeAgent")]),
|
|
144
|
-
adapter: "gemini", verifiedPayload: true,
|
|
145
|
-
notes: "Payload verified against geminicli.com/docs/hooks/reference: stdin is flat (session_id, cwd, hook_event_name, tool_name, tool_input, prompt), not the nested tool_call shape the \"gemini\" adapter also tolerates as a defensive fallback. Also installs BeforeAgent so a disabled agent is stopped before the prompt reaches the model, not only at its first tool call; both events accept {decision: \"deny\", reason} and both also honor exit code 2 (see client.ts's EXIT_2_FAILS_CLOSED)."
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
id: "opencode", name: "OpenCode",
|
|
149
|
-
configs: [".config/opencode/opencode.json", ".config/opencode/opencode.jsonc"], artifacts: [".local/share/opencode"],
|
|
150
|
-
hookConfigPath: null, pluginPath: ".opencode/plugins/beam.ts", hookEventName: "tool.execute.before",
|
|
151
|
-
mergeHookConfig: null, unmergeHookConfig: null, adapter: "passthrough", verifiedPayload: true,
|
|
152
|
-
notes: "Uses OpenCode's native tool.execute.before plugin hook."
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: "hermes", name: "Hermes Agent",
|
|
156
|
-
configs: [".hermes"], artifacts: [],
|
|
157
|
-
hookConfigPath: ".hermes/config.yaml", hookEventName: "pre_tool_call",
|
|
158
|
-
mergeHookConfig: null, unmergeHookConfig: null, customInstall: true,
|
|
159
|
-
adapter: "pre-tool-alias", verifiedPayload: true,
|
|
160
|
-
notes: "Installs a shell hook under hooks.pre_tool_call in ~/.hermes/config.yaml (default profile), between beam:begin/beam:end comments. Stdin fields (hook_event_name, tool_name, tool_input, session_id, cwd) and the block contract ({decision: \"block\", reason} or exit 2) verified against hermes-agent.nousresearch.com/docs/user-guide/features/hooks; tool_input shapes per Hermes tool are not. Hermes asks you to approve the hook on first use, and a hook that errors or times out fails open unless fail_closed is set."
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: "openclaw", name: "OpenClaw",
|
|
164
|
-
configs: [".openclaw"], artifacts: [],
|
|
165
|
-
hookConfigPath: null, hookEventName: "before_tool_call",
|
|
166
|
-
mergeHookConfig: null, unmergeHookConfig: null, customInstall: true,
|
|
167
|
-
adapter: "pre-tool-alias", verifiedPayload: false,
|
|
168
|
-
notes: "OpenClaw has no hook config file: beam writes a small plugin that registers api.on(\"before_tool_call\") and links it with 'openclaw plugins install --link' and 'openclaw plugins enable'. Registration and the { block, blockReason } return follow docs.openclaw.ai/plugins/hooks and published plugins, but the event field names are not printed in a reference, so the plugin reads them defensively; treat enforcement as unverified until you see a real event."
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
id: "jev", name: "Jev (TypeSafe AI)",
|
|
172
|
-
// TypeSafe's own Jev tooling and at least half a dozen independent third-party CLIs
|
|
173
|
-
// (jev-cli, jev-axi, jev-guard, ...) each pick their own config layout, so unlike the
|
|
174
|
-
// agents above there's no single vendor-documented directory or hook contract to target.
|
|
175
|
-
// These are the paths seen across that ecosystem as of Sept 2026; treat as best-effort.
|
|
176
|
-
configs: [".config/jev", ".config/jev-axi", ".jev"], artifacts: [".config/jev-axi/stats"],
|
|
177
|
-
hookConfigPath: null, hookEventName: null,
|
|
178
|
-
mergeHookConfig: null, unmergeHookConfig: null,
|
|
179
|
-
adapter: "generic", verifiedPayload: false, unverifiedEcosystem: true,
|
|
180
|
-
notes: "Jev is TypeSafe AI's non-generative 'System One' judgment model (launched 2026-09-15), " +
|
|
181
|
-
"increasingly wired into other agents' tool-call gating (e.g. jev-guard) rather than run as " +
|
|
182
|
-
"a coding agent itself. No single vendor hook contract exists yet across the many third-party " +
|
|
183
|
-
"CLIs wrapping it, so this entry is discovery-only (config/artifact presence), not installable."
|
|
184
|
-
},
|
|
185
|
-
];
|
|
4
|
+
export const AGENTS = HOOK_CATALOG.map(spec => ({
|
|
5
|
+
id: spec.id, name: spec.name, configs: spec.id === "copilot-cli" ? [".copilot/config.json"] : [spec.detect], artifacts: { "claude-code": [".claude/projects"], codex: [".codex/sessions", ".codex/archived_sessions"], cursor: [".cursor/projects"], "copilot-cli": [".copilot/session-state"], gemini: [".gemini/tmp"], opencode: [".local/share/opencode"] }[spec.id] ?? [],
|
|
6
|
+
hookConfigPath: spec.format === "plugin" ? null : spec.path,
|
|
7
|
+
pluginPath: spec.format === "plugin" ? spec.path : null,
|
|
8
|
+
hookEventName: spec.events.find(e => hookKind(e) === "pre") ?? null,
|
|
9
|
+
hookEvents: spec.events,
|
|
10
|
+
customInstall: true,
|
|
11
|
+
mergeHookConfig: (existing, command) => mergeHookSpec(spec, existing, command),
|
|
12
|
+
unmergeHookConfig: (existing, own) => unmergeHookSpec(spec, existing, own),
|
|
13
|
+
adapter: ["claude-code", "codex", "cursor"].includes(spec.id) ? "passthrough" : spec.id === "hermes" ? "pre-tool-alias" : spec.id === "copilot-cli" || spec.id === "vscode" ? "copilot-camel" : spec.id === "gemini" ? "gemini" : "generic",
|
|
14
|
+
verifiedPayload: ["claude-code", "codex", "cursor", "copilot-cli", "gemini", "hermes"].includes(spec.id),
|
|
15
|
+
notes: "Configuration and payload mapping based on Numbat's host contracts; validate with a live session on your agent version. See docs/agent-hooks.md.",
|
|
16
|
+
}));
|
|
17
|
+
AGENTS.push({
|
|
18
|
+
id: "jev", name: "Jev (TypeSafe AI)",
|
|
19
|
+
// TypeSafe's own Jev tooling and at least half a dozen independent third-party CLIs
|
|
20
|
+
// (jev-cli, jev-axi, jev-guard, ...) each pick their own config layout, so unlike the
|
|
21
|
+
// agents above there's no single vendor-documented directory or hook contract to target.
|
|
22
|
+
// These are the paths seen across that ecosystem as of Sept 2026; treat as best-effort.
|
|
23
|
+
configs: [".config/jev", ".config/jev-axi", ".jev"], artifacts: [".config/jev-axi/stats"],
|
|
24
|
+
hookConfigPath: null, hookEventName: null,
|
|
25
|
+
mergeHookConfig: null, unmergeHookConfig: null,
|
|
26
|
+
adapter: "generic", verifiedPayload: false, unverifiedEcosystem: true,
|
|
27
|
+
notes: "Jev is TypeSafe AI's non-generative 'System One' judgment model (launched 2026-09-15), " +
|
|
28
|
+
"increasingly wired into other agents' tool-call gating (e.g. jev-guard) rather than run as " +
|
|
29
|
+
"a coding agent itself. No single vendor hook contract exists yet across the many third-party " +
|
|
30
|
+
"CLIs wrapping it, so this entry is discovery-only (config/artifact presence), not installable."
|
|
31
|
+
});
|
|
186
32
|
export function findAgent(id) {
|
|
187
33
|
return AGENTS.find(a => a.id === id);
|
|
188
34
|
}
|
package/dist/agents.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The rolling window an action burst is measured over. */
|
|
2
|
+
export declare const ANOMALY_WINDOW_MS: number;
|
|
3
|
+
/** High/critical-severity actions within the window that count as a burst. */
|
|
4
|
+
export declare const ANOMALY_THRESHOLD = 5;
|
|
5
|
+
export interface AnomalyResult {
|
|
6
|
+
triggered: boolean;
|
|
7
|
+
count: number;
|
|
8
|
+
windowMinutes: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Records one HIGH/CRITICAL-severity action for `sessionId` (a LOW/MEDIUM `riskLevel` is a
|
|
12
|
+
* no-op: it still prunes/persists housekeeping but never adds an event or can trigger), and
|
|
13
|
+
* reports whether this session has hit `ANOMALY_THRESHOLD` such actions within
|
|
14
|
+
* `ANOMALY_WINDOW_MS`. `home` is the beam data directory (injectable for tests).
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordAndCheckAnomaly(sessionId: string, riskLevel: string, home?: string): Promise<AnomalyResult>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Behavioral anomaly detection: flags a burst of high/critical-severity actions from one agent
|
|
2
|
+
// session within a short window, independent of whether any single action was individually
|
|
3
|
+
// blocked. An agent that's been talked into a chain of risky actions (prompt injection, a
|
|
4
|
+
// compromised skill, a confused multi-step plan) often shows this shape even when each step
|
|
5
|
+
// individually only rates "ask" rather than "deny" -- worth a human's attention as a pattern,
|
|
6
|
+
// not just as isolated findings. Node-only (fs-backed session state), called from the CLI hook
|
|
7
|
+
// path in client.ts only -- same separation from data-detectors.ts/rule-catalog.ts as
|
|
8
|
+
// entity-redaction.ts, which stay pure so they can keep backing the edge-runtime SDK path.
|
|
9
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { getDataDirectory } from "./config.js";
|
|
12
|
+
import { withFileLock } from "./file-lock.js";
|
|
13
|
+
function activityPath(home) {
|
|
14
|
+
return join(home, "behavioral-activity.json");
|
|
15
|
+
}
|
|
16
|
+
async function readActivity(home) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(await readFile(activityPath(home), "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function writeActivity(map, home) {
|
|
25
|
+
const path = activityPath(home);
|
|
26
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
27
|
+
await writeFile(path, `${JSON.stringify(map, null, 2)}\n`, { mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
/** The rolling window an action burst is measured over. */
|
|
30
|
+
export const ANOMALY_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
|
|
31
|
+
/** High/critical-severity actions within the window that count as a burst. */
|
|
32
|
+
export const ANOMALY_THRESHOLD = 5;
|
|
33
|
+
// A session with no new high/critical event in this long is dropped entirely, so the file
|
|
34
|
+
// doesn't grow forever -- only timestamps live here, nothing sensitive either way.
|
|
35
|
+
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
/**
|
|
37
|
+
* Records one HIGH/CRITICAL-severity action for `sessionId` (a LOW/MEDIUM `riskLevel` is a
|
|
38
|
+
* no-op: it still prunes/persists housekeeping but never adds an event or can trigger), and
|
|
39
|
+
* reports whether this session has hit `ANOMALY_THRESHOLD` such actions within
|
|
40
|
+
* `ANOMALY_WINDOW_MS`. `home` is the beam data directory (injectable for tests).
|
|
41
|
+
*/
|
|
42
|
+
export async function recordAndCheckAnomaly(sessionId, riskLevel, home = getDataDirectory()) {
|
|
43
|
+
if (!sessionId)
|
|
44
|
+
return { triggered: false, count: 0, windowMinutes: ANOMALY_WINDOW_MS / 60_000 };
|
|
45
|
+
// Locked end-to-end (read, mutate, write) so concurrent tool calls in the same turn -- each
|
|
46
|
+
// spawning its own `beam hook` process -- don't lose each other's increments to a read-write
|
|
47
|
+
// race (see file-lock.ts). Without this, a genuine 5-action parallel burst could collapse to
|
|
48
|
+
// a single persisted event, since every process would independently read "0 prior events".
|
|
49
|
+
return withFileLock(activityPath(home), async () => {
|
|
50
|
+
const map = await readActivity(home);
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
for (const [id, session] of Object.entries(map)) {
|
|
53
|
+
const last = session.events.at(-1) ?? 0;
|
|
54
|
+
if (now - last > SESSION_TTL_MS)
|
|
55
|
+
delete map[id];
|
|
56
|
+
}
|
|
57
|
+
const session = map[sessionId] ?? { events: [] };
|
|
58
|
+
if (riskLevel === "HIGH" || riskLevel === "CRITICAL")
|
|
59
|
+
session.events.push(now);
|
|
60
|
+
session.events = session.events.filter(at => now - at <= ANOMALY_WINDOW_MS);
|
|
61
|
+
map[sessionId] = session;
|
|
62
|
+
await writeActivity(map, home).catch(() => { });
|
|
63
|
+
return { triggered: session.events.length >= ANOMALY_THRESHOLD, count: session.events.length, windowMinutes: ANOMALY_WINDOW_MS / 60_000 };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=behavioral-anomaly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behavioral-anomaly.js","sourceRoot":"","sources":["../src/behavioral-anomaly.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { configuredPort, installService, serviceLogPaths, serviceStatus, startSe
|
|
|
11
11
|
import { waitForCollectorUp } from "./collector-health.js";
|
|
12
12
|
import { getBeamHome, getCollectorUrl, getDataDirectory, getIdentityPath } from "./config.js";
|
|
13
13
|
import { clearIdentity, readIdentity } from "./enroll.js";
|
|
14
|
-
import { startConnect } from "./connect.js";
|
|
14
|
+
import { PlanLimitError, startConnect } from "./connect.js";
|
|
15
15
|
import { runSetup, promptYesNo } from "./setup.js";
|
|
16
16
|
import { installEnterprisePackage } from "./enterprise-install.js";
|
|
17
17
|
import { fetchAccount, revokeDevice } from "./account.js";
|
|
@@ -423,7 +423,20 @@ program.command("connect")
|
|
|
423
423
|
const session = await startConnect();
|
|
424
424
|
console.log(`Connect Beam CLI to Agentbeam:\n${cyan(session.url)}\n`);
|
|
425
425
|
openBrowser(session.url);
|
|
426
|
-
|
|
426
|
+
let identity;
|
|
427
|
+
try {
|
|
428
|
+
identity = await withSpinner("Waiting for you to finish in the browser", () => session.poll());
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (!(error instanceof PlanLimitError))
|
|
432
|
+
throw error;
|
|
433
|
+
console.error(`✖ ${error.message}`);
|
|
434
|
+
console.error(existing
|
|
435
|
+
? " This device stays connected as before."
|
|
436
|
+
: " Beam keeps protecting this machine locally. Run 'beam connect' again once there's room.");
|
|
437
|
+
process.exitCode = 1;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
427
440
|
console.log(`${green("✔")} Connected device ${identity.deviceId}\n` +
|
|
428
441
|
` org: ${identity.orgId}\n` +
|
|
429
442
|
` api: ${identity.apiBase}\n` +
|
|
@@ -844,27 +857,31 @@ mcp.command("proxy")
|
|
|
844
857
|
.argument("[args...]", "arguments passed to the MCP server")
|
|
845
858
|
.action(async (command, args) => { process.exitCode = await runMcpProxy(command, args ?? []); });
|
|
846
859
|
program.command("hook")
|
|
847
|
-
.description("
|
|
860
|
+
.description("Capture an agent lifecycle payload and apply workspace policy at supported pre-action gates")
|
|
848
861
|
.argument("[source-agent]", "reporting agent id, e.g. claude-code, codex, cursor, copilot-cli", "claude-code")
|
|
849
|
-
.
|
|
862
|
+
.option("--event <name>", "Native event name supplied by the installed hook")
|
|
863
|
+
.action(async (sourceAgent, options) => captureHook(sourceAgent, undefined, options.event));
|
|
850
864
|
const agent = program.command("agent").description("Discover and configure supported AI agents");
|
|
851
865
|
agent.command("list")
|
|
852
866
|
.alias("ls")
|
|
853
867
|
.description("List supported agents and their hook payload verification status")
|
|
854
|
-
.
|
|
868
|
+
.option("--events", "Include all configured lifecycle event names")
|
|
869
|
+
.action((options) => {
|
|
855
870
|
const rows = AGENTS.map(a => [
|
|
856
871
|
a.id,
|
|
857
872
|
a.name,
|
|
858
|
-
a.
|
|
873
|
+
a.id === "openhands" ? "project-scoped" : a.customInstall || a.hookConfigPath ? "installable" : "discovery only",
|
|
859
874
|
a.verifiedPayload ? "payload verified" : "payload best-effort",
|
|
875
|
+
...(options.events ? [(a.hookEvents ?? []).join(", ")] : []),
|
|
860
876
|
]);
|
|
861
|
-
console.log(renderColumns(["id", "name", "status", "payload"], rows));
|
|
877
|
+
console.log(renderColumns(["id", "name", "status", "payload", ...(options.events ? ["events"] : [])], rows));
|
|
862
878
|
});
|
|
863
879
|
agent.command("install")
|
|
864
880
|
.description("Wire beam's hook into an agent's own hook config file, without disturbing existing hooks")
|
|
865
881
|
.argument("<agent>", "agent id, e.g. claude-code, codex, cursor, copilot-cli")
|
|
866
|
-
.
|
|
867
|
-
|
|
882
|
+
.option("--settings <path>", "Explicit hook config path (required for OpenHands project hooks)")
|
|
883
|
+
.action(async (agentId, options) => {
|
|
884
|
+
const result = await installHook(agentId, undefined, options.settings);
|
|
868
885
|
await recordExpectedHooks();
|
|
869
886
|
console.log(result.alreadyInstalled ? `✔ Already installed for ${result.agent}\n → ${result.path}` : `✔ Installed beam hook for ${result.agent}\n → ${result.path}`);
|
|
870
887
|
if (result.note)
|
|
@@ -874,6 +891,7 @@ agent.command("uninstall")
|
|
|
874
891
|
.description("Detach beam's hook from a single agent's own hook config, leaving everything else installed")
|
|
875
892
|
.argument("<agent>", "agent id, e.g. claude-code, codex, cursor, copilot-cli")
|
|
876
893
|
.option("--yes", "skip the confirmation prompt")
|
|
894
|
+
.option("--settings <path>", "Explicit hook config path")
|
|
877
895
|
.action(async (agentId, options) => {
|
|
878
896
|
const identity = await readIdentity();
|
|
879
897
|
const proceed = await confirmControlAction(`This will detach beam's hook from ${agentId}. Its activity will no longer be observed or policy-enforced by beam.`, identity, options.yes);
|
|
@@ -883,7 +901,7 @@ agent.command("uninstall")
|
|
|
883
901
|
}
|
|
884
902
|
if (identity)
|
|
885
903
|
await reportControlEvent(`agent-uninstall-${agentId}`, `Beam hook detached from ${agentId} on ${identity.hostname}`, `This device ran 'beam agent uninstall ${agentId}'. ${agentId} is no longer being observed or policy-enforced by beam.`);
|
|
886
|
-
const result = await uninstallHook(agentId);
|
|
904
|
+
const result = await uninstallHook(agentId, undefined, options.settings);
|
|
887
905
|
await recordExpectedHooks();
|
|
888
906
|
console.log(result.removed ? `✔ Detached beam's hook from ${result.agent}\n → ${result.path}` : `— No beam hook found for ${result.agent}.`);
|
|
889
907
|
if (result.note)
|