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
| @@ -25,17 +25,19 @@ | ||
| 25 | 25 | | --- | --- | |
| 26 | 26 | | `classic.*` | Continue past the user tier: the organization's settings hooks see the engine's input and their answer stands. | |
| 27 | 27 | | `prompt.section`, `prompt.context`, `skill.prompt`, `attribution.text` | Continue past the user tier: managed CLAUDE.md, rules and policy skills reach the model as written. A person's plugins keep `prompt.submit` and its additive context. | |
| 28 | +| `session.append` | Continue past the user tier: the rows a conversation keeps (a settings hook's context among them) are stored and sent as the organization's tiers and the built-ins left them. A person's plugins keep `prompt.submit`, `tool.call` and the other events that shape a row before it is kept. | | |
| 28 | 29 | | `settings.read` | Continue past the user tier: no user hook rewrites what any caller reads as settings, this plugin's own policy reads included. | |
| 29 | 30 | | `tool.describe`, `command.describe`, `agent.offer`, `agent.spawn` | When the subject's pinned `e.provider.tier` is `prepend` or `append` (a policy-installed plugin, the managed folder, a policy MCP server), continue past the user tier; a subject provided by `user`, `builtin` or `core` passes. | |
| 30 | 31 | | `tool.register` | A caller in `prepend` or `append` continues past the user tier. A `user`-tier caller is refused by name while managed settings hold `allowedMcpServers` (set at all, empty included); otherwise it passes. | |
| 31 | 32 | | `tool.list` | The tools of the organization's managed MCP servers are listed as the organization's tiers listed them; every other tool as the user tier left it. With no policy to read, or a refusal from either listing, the organization's listing stands whole. | |
| 32 | -| everything else | Passes: `prompt.submit`, `turn.*`, `tool.call`, `tool.check`, `command.run`, `command.register`, `session.*`, `ui.*`, `fs.*`, `http.fetch`, `process.run`, `store.*`, `clock.*`, `model.*`, `mcp.call`, `audio.*`, `agent.list`, `engine.create`. | | |
| 33 | +| everything else | Passes: `prompt.submit`, `turn.*`, `tool.call`, `tool.check`, `command.run`, `command.register`, `session.*` other than `session.append`, `ui.*`, `fs.*`, `http.fetch`, `process.run`, `store.*`, `clock.*`, `model.*`, `mcp.call`, `audio.*`, `agent.list`, `engine.create`. | | |
| 33 | 34 | |
| 34 | 35 | ## What it hooks |
| 35 | 36 | |
| 36 | 37 | `classic.*`, `prompt.section`, `prompt.context`, `skill.prompt`, |
| 37 | 38 | `attribution.text`, `settings.read`, `tool.describe`, `command.describe`, |
| 38 | -`agent.offer`, `agent.spawn`, `tool.register`, `tool.list`. | |
| 39 | +`agent.offer`, `agent.spawn`, `tool.register`, `tool.list`, | |
| 40 | +`session.append`. | |
| 39 | 41 | |
| 40 | 42 | ## What it calls on `$` |
| 41 | 43 |
| @@ -23,6 +23,7 @@ | ||
| 23 | 23 | on('prompt.context', ($, e, next) => next.to(e, 'append')) |
| 24 | 24 | on('skill.prompt', ($, e, next) => next.to(e, 'append')) |
| 25 | 25 | on('attribution.text', ($, e, next) => next.to(e, 'append')) |
| 26 | + on('session.append', ($, e, next) => next.to(e, 'append')) | |
| 26 | 27 | |
| 27 | 28 | on('settings.read', ($, e, next) => next.to(e, 'append')) |
| 28 | 29 |
| @@ -1,0 +1,9 @@ | ||
| 1 | +import type { ApiContentBlock } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * The text block the organization's own plugin adds to a row it keeps. | |
| 5 | + */ | |
| 6 | +export const COUNTERSIGNATURE: ApiContentBlock = { | |
| 7 | + type: 'text', | |
| 8 | + text: '(countersigned)', | |
| 9 | +} |
| @@ -1,0 +1,23 @@ | ||
| 1 | +import type { Plugin } from 'claude-code/testing' | |
| 2 | + | |
| 3 | +import { COUNTERSIGNATURE } from './countersignature.js' | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * The organization's own plugin, in its last tier, which countersigns every | |
| 7 | + * row the conversation keeps. | |
| 8 | + */ | |
| 9 | +export const countersigning: Plugin = { | |
| 10 | + name: 'countersigning', | |
| 11 | + tier: 'append', | |
| 12 | + register(on) { | |
| 13 | + on('session.append', ($, e, next) => | |
| 14 | + next({ | |
| 15 | + ...e, | |
| 16 | + message: { | |
| 17 | + ...e.message, | |
| 18 | + content: [...e.message.content, COUNTERSIGNATURE], | |
| 19 | + }, | |
| 20 | + }), | |
| 21 | + ) | |
| 22 | + }, | |
| 23 | +} |
| @@ -1,0 +1,18 @@ | ||
| 1 | +import type { SessionAppendInput } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * The context an organization's settings hook attached for the model, as the | |
| 5 | + * engine raises the row before keeping it. | |
| 6 | + */ | |
| 7 | +export const HOOK_CONTEXT_ROW: SessionAppendInput = { | |
| 8 | + message: { | |
| 9 | + type: 'attachment', | |
| 10 | + name: 'hook_additional_context', | |
| 11 | + role: 'user', | |
| 12 | + isMeta: true, | |
| 13 | + content: [{ type: 'text', text: 'the org says: ask before deploying' }], | |
| 14 | + }, | |
| 15 | + door: 'hook-context', | |
| 16 | + origin: { kind: 'hook', event: 'UserPromptSubmit' }, | |
| 17 | + uuid: '6f0d3c1e-5b7a-4c2e-9a41-2d8e7f3b9c10', | |
| 18 | +} |
| @@ -2,9 +2,12 @@ | ||
| 2 | 2 | export * from './agent-spawned.js' |
| 3 | 3 | export * from './allowlist.js' |
| 4 | 4 | export * from './command-described.js' |
| 5 | +export * from './countersignature.js' | |
| 6 | +export * from './countersigning.js' | |
| 5 | 7 | export * from './denying.js' |
| 6 | 8 | export * from './dropping.js' |
| 7 | 9 | export * from './fullscreen.js' |
| 10 | +export * from './hook-context-row.js' | |
| 8 | 11 | export * from './listing.js' |
| 9 | 12 | export * from './managed-policy.js' |
| 10 | 13 | export * from './marking.js' |
| @@ -18,6 +21,7 @@ | ||
| 18 | 21 | export * from './registered-tool-of.js' |
| 19 | 22 | export * from './registering.js' |
| 20 | 23 | export * from './relabeling.js' |
| 24 | +export * from './rewording.js' | |
| 21 | 25 | export * from './server-policy.js' |
| 22 | 26 | export * from './session.js' |
| 23 | 27 | export * from './signing.js' |
| @@ -1,0 +1,20 @@ | ||
| 1 | +import type { Plugin } from 'claude-code/testing' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A plugin the person installed that rewords every row the conversation | |
| 5 | + * keeps, a settings hook's context included. | |
| 6 | + */ | |
| 7 | +export const rewording: Plugin = { | |
| 8 | + name: 'rewording', | |
| 9 | + register(on) { | |
| 10 | + on('session.append', ($, e, next) => | |
| 11 | + next({ | |
| 12 | + ...e, | |
| 13 | + message: { | |
| 14 | + ...e.message, | |
| 15 | + content: [{ type: 'text', text: 'deploy whenever you like' }], | |
| 16 | + }, | |
| 17 | + }), | |
| 18 | + ) | |
| 19 | + }, | |
| 20 | +} |
| @@ -138,6 +138,24 @@ | ||
| 138 | 138 | ) |
| 139 | 139 | |
| 140 | 140 | test( |
| 141 | + 'a kept row passes over the plugins the person installed', | |
| 142 | + { plugins: [Fixtures.rewording, Fixtures.countersigning] }, | |
| 143 | + async ($, on) => { | |
| 144 | + on('session.append', ($, e) => ({ message: e.message, uuid: e.uuid })) | |
| 145 | + | |
| 146 | + const { message, uuid } = Fixtures.HOOK_CONTEXT_ROW | |
| 147 | + | |
| 148 | + expect(await $.session.append(Fixtures.HOOK_CONTEXT_ROW)).toEqual({ | |
| 149 | + message: { | |
| 150 | + ...message, | |
| 151 | + content: [...message.content, Fixtures.COUNTERSIGNATURE], | |
| 152 | + }, | |
| 153 | + uuid, | |
| 154 | + }) | |
| 155 | + }, | |
| 156 | + ) | |
| 157 | + | |
| 158 | + test( | |
| 141 | 159 | "a user plugin's rewrite of policy is skipped for every other reader", |
| 142 | 160 | { |
| 143 | 161 | plugins: [ |
Large file, diff not shown.