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/ 3deb873 · 12 days ago History
208 lines · 6.0 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 if (!HeldVerdict.holdsOver(held, answer)) {
120 return answer
121 }
122
123 const kind = HeldVerdict.heldKind(held)
124
125 for (const mod of mods.filter(name => !told.has(`${name} ${kind}`))) {
126 told.add(`${mod} ${kind}`)
127 $.ui.log(
128 HeldVerdict.heldNotice(
129 mod,
130 e.tool,
131 held,
132 HeldVerdict.isLooser(answer, held) ? 'lift' : 'reword',
133 ),
134 )
135 }
136
137 return held
138 }).catch(async ($, e, next) => {
139 const last = await next(e).catch(() => undefined)
140
141 const shouldVouch = await Policy.decidedByPolicy(
142 readPolicy(() => $.settings.read(Policy.SOURCE)),
143 Policy.denyRulesHold,
144 )
145
146 return shouldVouch ? HeldVerdict.caughtAnswer(last, next.trace) : last
147 })
148
149 on('ui.log', ($, e, next) => {
150 const isOrgs =
151 next.origin.tier === 'prepend' || next.origin.tier === 'append'
152
153 return isOrgs ? next.to(e, 'append') : next(e)
154 })
155
156 on('env.get', ($, e, next) => {
157 const isOrgs =
158 next.origin.tier === 'prepend' || next.origin.tier === 'append'
159
160 return isOrgs ? next.to(e, 'append') : next(e)
161 })
162
163 on('env.set', async ($, e, next) => {
164 const isOrgs =
165 next.origin.tier === 'prepend' || next.origin.tier === 'append'
166
167 if (isOrgs) {
168 return next.to(e, 'append')
169 }
170
171 const isPinned = await Policy.decidedByPolicy(
172 readPolicy(() => $.settings.read(Policy.SOURCE)),
173 policy => Policy.isPinnedVariable(policy, e.name),
174 )
175
176 if (!isPinned) {
177 return next(e)
178 }
179
180 const isTheirs = next.origin.tier === 'user'
181
182 return isTheirs
183 ? { deny: pinnedVariableRefusal(e.name) }
184 : next.to(e, 'append')
185 }).catch(($, e, next) => {
186 if (next.called) {
187 return next(e)
188 }
189
190 const isTheirs = next.origin.tier === 'user'
191
192 return isTheirs
193 ? { deny: pinnedVariableRefusal(e.name) }
194 : next.to(e, 'append')
195 })
196
197 on('plugin.register', { tier: 'user' }, async ($, e, next) => {
198 admittedAs.set(e.name, e.provenance)
199
200 return Policy.isManagedModsOnly(await $.settings.read(Policy.SOURCE))
201 ? { refuse: managedModsOnlyRefusal(e.name) }
202 : next(e)
203 }).catch(($, e, next) => {
204 $.ui.log(admissionFailure(e.name, next.error), { to: 'debug' })
205
206 return next.called ? next(e) : { refuse: managedModsOnlyRefusal(e.name) }
207 })
208}