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

sec-default: the rows a conversation keeps continue past the user tier; the declarations carry session.appendBrowse files
poteat authored 27 days ago parent 7779afb · commit 290e06df6dc0
9 changed files+97 -2compared with 7779afb
Mmods/sec-default/README.md
Mmods/sec-default/hooks/register.ts
Amods/sec-default/tests/fixtures/countersignature.ts
Amods/sec-default/tests/fixtures/countersigning.ts
Amods/sec-default/tests/fixtures/hook-context-row.ts
Mmods/sec-default/tests/fixtures/index.ts
Amods/sec-default/tests/fixtures/rewording.ts
Mmods/sec-default/tests/register.test.ts
Mmods/types/claude-code.d.ts
@@ -25,17 +25,19 @@
2525 | --- | --- |
2626 | `classic.*` | Continue past the user tier: the organization's settings hooks see the engine's input and their answer stands. |
2727 | `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. |
2829 | `settings.read` | Continue past the user tier: no user hook rewrites what any caller reads as settings, this plugin's own policy reads included. |
2930 | `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. |
3031 | `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. |
3132 | `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`. |
3334
3435 ## What it hooks
3536
3637 `classic.*`, `prompt.section`, `prompt.context`, `skill.prompt`,
3738 `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`.
3941
4042 ## What it calls on `$`
4143
@@ -23,6 +23,7 @@
2323 on('prompt.context', ($, e, next) => next.to(e, 'append'))
2424 on('skill.prompt', ($, e, next) => next.to(e, 'append'))
2525 on('attribution.text', ($, e, next) => next.to(e, 'append'))
26+ on('session.append', ($, e, next) => next.to(e, 'append'))
2627
2728 on('settings.read', ($, e, next) => next.to(e, 'append'))
2829
@@ -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 @@
22 export * from './agent-spawned.js'
33 export * from './allowlist.js'
44 export * from './command-described.js'
5+export * from './countersignature.js'
6+export * from './countersigning.js'
57 export * from './denying.js'
68 export * from './dropping.js'
79 export * from './fullscreen.js'
10+export * from './hook-context-row.js'
811 export * from './listing.js'
912 export * from './managed-policy.js'
1013 export * from './marking.js'
@@ -18,6 +21,7 @@
1821 export * from './registered-tool-of.js'
1922 export * from './registering.js'
2023 export * from './relabeling.js'
24+export * from './rewording.js'
2125 export * from './server-policy.js'
2226 export * from './session.js'
2327 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 @@
138138 )
139139
140140 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(
141159 "a user plugin's rewrite of policy is skipped for every other reader",
142160 {
143161 plugins: [
Large file, diff not shown.