spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / sec-default / hooks / register.ts
GitHub Actions (publish-mods)chore: Update mods/ 5a2676e · 2 days ago History
210 lines · 6.1 KBRaw
1import type { On } from 'claude-code'
2
3import { admissionFailure } from './admission-failure'
4import HeldVerdict from './held-verdict'
5import { managedModsOnlyRefusal } from './managed-mods-only-refusal'
6import { pastUsers } from './past-users'
7import { pinnedVariableRefusal } from './pinned-variable-refusal'
8import Policy from './policy'
9import { isSkillRow } from './skill-row'
10import {
11 registerRefusalOf,
12 TOOL_REGISTER_REFUSAL,
13} from './tool-register-refusal'
14
15/**
16 * The built-in's hooks, seated outermost: each keeps one control an
17 * organization has today out of reach of the plugins a person installs.
18 *
19 * Three moves: continue past the user tier (`next.to(e, "append")`), refuse
20 * a user-tier caller or module by name, or pass. Provenance is the event's
21 * pinned `provider` or `tier`; policy is `$.settings.read`; fail closed.
22 *
23 * @param on the engine's registrar
24 */
25export function register(on: On) {
26 const readPolicy = Policy.createPolicyMemo(Policy.POLICY_MEMO_MS)
27 const told = new Set<string>()
28 const admittedAs = new Map<string, string>()
29
30 on('classic.*', ($, e, next) => next.to(e, 'append'))
31
32 on('prompt.section', ($, e, next) => next.to(e, 'append'))
33 on('prompt.context', ($, e, next) => next.to(e, 'append'))
34 on('prompt.attachment', ($, e, next) => next.to(e, 'append'))
35 on('prompt.compose', ($, e, next) => next.to(e, 'append'))
36 on('skill.prompt', ($, e, next) => next.to(e, 'append'))
37 on('attribution.text', ($, e, next) => next.to(e, 'append'))
38
39 on('session.append', { door: 'attachment' }, ($, e, next) =>
40 next.to(e, 'append'),
41 )
42 on('session.append', { door: 'hook-context' }, ($, e, next) =>
43 next.to(e, 'append'),
44 )
45 on('session.append', { door: 'compaction' }, ($, e, next) =>
46 next.to(e, 'append'),
47 )
48 on('session.append', { door: 'tool-message' }, ($, e, next) =>
49 isSkillRow(e) ? next.to(e, 'append') : next(e),
50 )
51 on('session.append', { door: 'note' }, ($, e, next) => {
52 const isOrgs =
53 next.origin.tier === 'prepend' || next.origin.tier === 'append'
54
55 return isOrgs ? next.to(e, 'append') : next(e)
56 })
57
58 on('session.compact', ($, e, next) => next.to(e, 'append'))
59
60 on('settings.read', ($, e, next) => next.to(e, 'append'))
61
62 on('tool.describe', ($, e, next) => pastUsers(e, next))
63 on('command.describe', ($, e, next) => pastUsers(e, next))
64 on('agent.offer', ($, e, next) => pastUsers(e, next))
65 on('agent.spawn', ($, e, next) => pastUsers(e, next))
66
67 on('tool.register', async ($, e, next) => {
68 const isOrgs =
69 next.origin.tier === 'prepend' || next.origin.tier === 'append'
70
71 if (isOrgs) {
72 return next.to(e, 'append')
73 }
74
75 const refusal =
76 next.origin.tier === 'user'
77 ? await readPolicy(() => $.settings.read(Policy.SOURCE))
78 .then(policy =>
79 registerRefusalOf(
80 policy,
81 next.origin.plugin,
82 e.name,
83 admittedAs.get(next.origin.plugin),
84 ),
85 )
86 .catch(() => TOOL_REGISTER_REFUSAL)
87 : undefined
88
89 return refusal === undefined ? next(e) : { deny: refusal }
90 })
91
92 on('tool.list', async ($, e, next) =>
93 Policy.managedToolsRestored(
94 await readPolicy(() => $.settings.read(Policy.SOURCE)).catch(
95 () => undefined,
96 ),
97 await next.to(e, 'append'),
98 await next(e),
99 ),
100 )
101
102 on('tool.check', async ($, e, next) => {
103 const answer = await next(e)
104 const mods = HeldVerdict.rewrittenByUsers(next.trace)
105
106 const shouldRecheck =
107 mods.length > 0 &&
108 (await Policy.decidedByPolicy(
109 readPolicy(() => $.settings.read(Policy.SOURCE)),
110 Policy.denyRulesHold,
111 ))
112
113 if (!shouldRecheck) {
114 return answer
115 }
116
117 const held = await next.to(e, 'append')
118
119 const { approver }: { tool: string; approver?: string } = e
120
121 if (!HeldVerdict.holdsOver(held, answer, approver)) {
122 return answer
123 }
124
125 const kind = HeldVerdict.heldKind(held)
126
127 for (const mod of mods.filter(name => !told.has(`${name} ${kind}`))) {
128 told.add(`${mod} ${kind}`)
129 $.ui.log(
130 HeldVerdict.heldNotice(
131 mod,
132 e.tool,
133 held,
134 HeldVerdict.isLooser(answer, held) ? 'lift' : 'reword',
135 ),
136 )
137 }
138
139 return held
140 }).catch(async ($, e, next) => {
141 const last = await next(e).catch(() => undefined)
142
143 const shouldVouch = await Policy.decidedByPolicy(
144 readPolicy(() => $.settings.read(Policy.SOURCE)),
145 Policy.denyRulesHold,
146 )
147
148 return shouldVouch ? HeldVerdict.caughtAnswer(last, next.trace) : last
149 })
150
151 on('ui.log', ($, e, next) => {
152 const isOrgs =
153 next.origin.tier === 'prepend' || next.origin.tier === 'append'
154
155 return isOrgs ? next.to(e, 'append') : next(e)
156 })
157
158 on('env.get', ($, e, next) => {
159 const isOrgs =
160 next.origin.tier === 'prepend' || next.origin.tier === 'append'
161
162 return isOrgs ? next.to(e, 'append') : next(e)
163 })
164
165 on('env.set', async ($, e, next) => {
166 const isOrgs =
167 next.origin.tier === 'prepend' || next.origin.tier === 'append'
168
169 if (isOrgs) {
170 return next.to(e, 'append')
171 }
172
173 const isPinned = await Policy.decidedByPolicy(
174 readPolicy(() => $.settings.read(Policy.SOURCE)),
175 policy => Policy.isPinnedVariable(policy, e.name),
176 )
177
178 if (!isPinned) {
179 return next(e)
180 }
181
182 const isTheirs = next.origin.tier === 'user'
183
184 return isTheirs
185 ? { deny: pinnedVariableRefusal(e.name) }
186 : next.to(e, 'append')
187 }).catch(($, e, next) => {
188 if (next.called) {
189 return next(e)
190 }
191
192 const isTheirs = next.origin.tier === 'user'
193
194 return isTheirs
195 ? { deny: pinnedVariableRefusal(e.name) }
196 : next.to(e, 'append')
197 })
198
199 on('plugin.register', { tier: 'user' }, async ($, e, next) => {
200 admittedAs.set(e.name, e.provenance)
201
202 return Policy.isManagedModsOnly(await $.settings.read(Policy.SOURCE))
203 ? { refuse: managedModsOnlyRefusal(e.name) }
204 : next(e)
205 }).catch(($, e, next) => {
206 $.ui.log(admissionFailure(e.name, next.error), { to: 'debug' })
207
208 return next.called ? next(e) : { refuse: managedModsOnlyRefusal(e.name) }
209 })
210}