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
| @@ -1,0 +1,14 @@ | ||
| 1 | +import type { AgentOfferInput, Origin } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * An agent type's offer as the engine asks for it, pinned to who provides | |
| 5 | + * the agent. | |
| 6 | + * | |
| 7 | + * @param agent the agent type | |
| 8 | + * @param provider who provides it, as the engine pinned it | |
| 9 | + * @returns the `agent.offer` input | |
| 10 | + */ | |
| 11 | +export const agentOffered = ( | |
| 12 | + agent: string, | |
| 13 | + provider: Origin, | |
| 14 | +): AgentOfferInput => ({ agent, description: 'd', source: 'plugin', provider }) |
| @@ -1,0 +1,20 @@ | ||
| 1 | +import type { AgentSpawnInput, Origin } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A subagent's spawn as the engine raises it, pinned to who provides the | |
| 5 | + * agent's definition. | |
| 6 | + * | |
| 7 | + * @param provider who provides the agent, as the engine pinned it | |
| 8 | + * @returns the `agent.spawn` input | |
| 9 | + */ | |
| 10 | +export const agentSpawned = (provider: Origin): AgentSpawnInput => ({ | |
| 11 | + tool_use_id: 't1', | |
| 12 | + prompt: 'list the files in src', | |
| 13 | + description: 'List files', | |
| 14 | + subagentType: 'general-purpose', | |
| 15 | + provider, | |
| 16 | + parentModel: 'the-model', | |
| 17 | + permissionMode: 'default', | |
| 18 | + background: false, | |
| 19 | + fork: false, | |
| 20 | +}) |
| @@ -1,0 +1,20 @@ | ||
| 1 | +import type { CommandDescribeInput, Origin } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A visible command's description as the engine asks for it, pinned to | |
| 5 | + * who provides the command. | |
| 6 | + * | |
| 7 | + * @param command the command's name, no slash | |
| 8 | + * @param provider who provides it, as the engine pinned it | |
| 9 | + * @returns the `command.describe` input | |
| 10 | + */ | |
| 11 | +export const commandDescribed = ( | |
| 12 | + command: string, | |
| 13 | + provider: Origin, | |
| 14 | +): CommandDescribeInput => ({ | |
| 15 | + command, | |
| 16 | + description: 'd', | |
| 17 | + isHidden: false, | |
| 18 | + immediate: false, | |
| 19 | + provider, | |
| 20 | +}) |
| @@ -1,0 +1,12 @@ | ||
| 1 | +import type { Plugin } from 'claude-code/testing' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A plugin the person installed that refuses every `$.tool.register` | |
| 5 | + * beneath it. | |
| 6 | + */ | |
| 7 | +export const denying: Plugin = { | |
| 8 | + name: 'denying', | |
| 9 | + register(on) { | |
| 10 | + on('tool.register', () => ({ deny: 'denied by denying' })) | |
| 11 | + }, | |
| 12 | +} |
| @@ -1,15 +1,31 @@ | ||
| 1 | +export * from './agent-offered.js' | |
| 2 | +export * from './agent-spawned.js' | |
| 1 | 3 | export * from './allowlist.js' |
| 4 | +export * from './command-described.js' | |
| 5 | +export * from './denying.js' | |
| 2 | 6 | export * from './dropping.js' |
| 3 | 7 | export * from './listing.js' |
| 8 | +export * from './managed-policy.js' | |
| 9 | +export * from './marking.js' | |
| 4 | 10 | export * from './memory.js' |
| 5 | 11 | export * from './no-allowlist.js' |
| 12 | +export * from './odd-providers.js' | |
| 13 | +export * from './org-providers.js' | |
| 14 | +export * from './policy-command.js' | |
| 15 | +export * from './policy-reads.js' | |
| 16 | +export * from './reading.js' | |
| 6 | 17 | export * from './registered-tool-of.js' |
| 7 | 18 | export * from './registering.js' |
| 8 | 19 | export * from './relabeling.js' |
| 20 | +export * from './server-policy.js' | |
| 9 | 21 | export * from './session.js' |
| 10 | 22 | export * from './signing.js' |
| 23 | +export * from './stripping.js' | |
| 24 | +export * from './subjects-echoed.js' | |
| 25 | +export * from './tool-described.js' | |
| 11 | 26 | export * from './tools.js' |
| 12 | 27 | export * from './tools-command.js' |
| 13 | 28 | export * from './tools-registered.js' |
| 29 | +export * from './user-reachable-providers.js' | |
| 14 | 30 | |
| 15 | 31 | export * as default from '.' |
| @@ -1,0 +1,10 @@ | ||
| 1 | +import type { Settings } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Managed settings with an MCP allowlist naming the corp server and one | |
| 5 | + * policy-installed plugin, `suite`. | |
| 6 | + */ | |
| 7 | +export const MANAGED_POLICY: Settings = { | |
| 8 | + allowedMcpServers: [{ serverName: 'corp' }], | |
| 9 | + enabledPlugins: { 'suite@corp-market': true }, | |
| 10 | +} |
| @@ -1,0 +1,17 @@ | ||
| 1 | +import type { Plugin } from 'claude-code/testing' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A plugin the person installed that marks all four subject events. | |
| 5 | + * | |
| 6 | + * A tool's description, a command's hidden flag, an agent's offer, a | |
| 7 | + * spawn's model. | |
| 8 | + */ | |
| 9 | +export const marking: Plugin = { | |
| 10 | + name: 'marking', | |
| 11 | + register(on) { | |
| 12 | + on('tool.describe', ($, e) => ({ description: `user: ${e.description}` })) | |
| 13 | + on('command.describe', ($, e, next) => next({ ...e, isHidden: true })) | |
| 14 | + on('agent.offer', () => ({ isOffered: false })) | |
| 15 | + on('agent.spawn', () => ({ model: 'user' })) | |
| 16 | + }, | |
| 17 | +} |
| @@ -1,0 +1,12 @@ | ||
| 1 | +import type { Origin } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Providers no site pins: tierless, or of a tier the engine does not have. | |
| 5 | + * | |
| 6 | + * Only a plugin's own `$` call can hand one down; sec-default fails closed | |
| 7 | + * on it. | |
| 8 | + */ | |
| 9 | +export const ODD_PROVIDERS: readonly Origin[] = [ | |
| 10 | + { plugin: 'x' }, | |
| 11 | + { plugin: 'x', tier: 'managed' }, | |
| 12 | +] as readonly unknown[] as readonly Origin[] |
| @@ -1,0 +1,12 @@ | ||
| 1 | +import type { Origin } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Providers in the organization's tiers: a policy-installed plugin | |
| 5 | + * (prepended by default, or appended), a policy MCP server, its folder. | |
| 6 | + */ | |
| 7 | +export const ORG_PROVIDERS: readonly Origin[] = [ | |
| 8 | + { plugin: 'suite@corp-market', tier: 'prepend' }, | |
| 9 | + { plugin: 'audit@corp-market', tier: 'append' }, | |
| 10 | + { plugin: 'mcp:corp', tier: 'prepend' }, | |
| 11 | + { plugin: 'policy', tier: 'prepend' }, | |
| 12 | +] |
| @@ -1,0 +1,10 @@ | ||
| 1 | +import type { CommandRunInput } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * `/policy` as the person types it: the reading plugin's command. | |
| 5 | + */ | |
| 6 | +export const POLICY_COMMAND: CommandRunInput = { | |
| 7 | + command: 'policy', | |
| 8 | + args: '', | |
| 9 | + origin: { kind: 'composer' }, | |
| 10 | +} |
| @@ -1,0 +1,21 @@ | ||
| 1 | +import type { On, Settings } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Answers every settings read beneath the plugins with the policy given, | |
| 5 | + * counting the reads. | |
| 6 | + * | |
| 7 | + * @param on the test's `on` | |
| 8 | + * @param policy the managed settings in force | |
| 9 | + * @returns how many reads reached the bottom so far | |
| 10 | + */ | |
| 11 | +export function policyReads(on: On, policy: Settings): () => number { | |
| 12 | + let reads = 0 | |
| 13 | + | |
| 14 | + on('settings.read', () => { | |
| 15 | + reads += 1 | |
| 16 | + | |
| 17 | + return { value: policy } | |
| 18 | + }) | |
| 19 | + | |
| 20 | + return () => reads | |
| 21 | +} |
| @@ -1,0 +1,14 @@ | ||
| 1 | +import type { Plugin } from 'claude-code/testing' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A plugin the person installed whose `/policy` answers the managed | |
| 5 | + * settings it reads, as JSON. | |
| 6 | + */ | |
| 7 | +export const reading: Plugin = { | |
| 8 | + name: 'reading', | |
| 9 | + register(on) { | |
| 10 | + on('command.run', { command: 'policy' }, async $ => ({ | |
| 11 | + text: JSON.stringify(await $.settings.read({ source: 'policy' })), | |
| 12 | + })) | |
| 13 | + }, | |
| 14 | +} |
| @@ -1,0 +1,9 @@ | ||
| 1 | +import type { Settings } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Managed settings that deliver the corp MCP server themselves, with no | |
| 5 | + * allowlist naming it: a tool policy in force all the same. | |
| 6 | + */ | |
| 7 | +export const SERVER_POLICY: Settings = { | |
| 8 | + managedMcpServers: { corp: { type: 'http', url: 'https://corp.example/x' } }, | |
| 9 | +} |
| @@ -1,0 +1,23 @@ | ||
| 1 | +import type { Plugin } from 'claude-code/testing' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A plugin the person installed that strips the MCP allowlist from every | |
| 5 | + * settings read beneath it, as one hiding the organization's policy would. | |
| 6 | + */ | |
| 7 | +export const stripping: Plugin = { | |
| 8 | + name: 'stripping', | |
| 9 | + register(on) { | |
| 10 | + on('settings.read', async ($, e, next) => { | |
| 11 | + const read = await next(e) | |
| 12 | + const { value } = read | |
| 13 | + | |
| 14 | + if (value === undefined) { | |
| 15 | + return read | |
| 16 | + } | |
| 17 | + | |
| 18 | + const { allowedMcpServers: _hidden, ...rest } = value | |
| 19 | + | |
| 20 | + return { value: rest } | |
| 21 | + }) | |
| 22 | + }, | |
| 23 | +} |
| @@ -1,0 +1,22 @@ | ||
| 1 | +import type { On } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Answers the four subject events beneath every plugin as the engine's | |
| 5 | + * core would. | |
| 6 | + * | |
| 7 | + * The description and hidden flag the chain left, the agent offered, the | |
| 8 | + * spawn on the model `core`. | |
| 9 | + * | |
| 10 | + * @param on the test's `on` | |
| 11 | + */ | |
| 12 | +export function subjectsEchoed(on: On) { | |
| 13 | + on('tool.describe', ($, e) => ({ description: e.description })) | |
| 14 | + | |
| 15 | + on('command.describe', ($, e) => ({ | |
| 16 | + description: e.description, | |
| 17 | + isHidden: e.isHidden, | |
| 18 | + })) | |
| 19 | + | |
| 20 | + on('agent.offer', () => ({ isOffered: true })) | |
| 21 | + on('agent.spawn', () => ({ model: 'core' })) | |
| 22 | +} |
| @@ -1,0 +1,14 @@ | ||
| 1 | +import type { Origin, ToolDescribeInput } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * A tool's description as the engine asks for it, `d`, pinned to who | |
| 5 | + * provides the tool. | |
| 6 | + * | |
| 7 | + * @param tool the tool's name | |
| 8 | + * @param provider who provides it, as the engine pinned it | |
| 9 | + * @returns the `tool.describe` input | |
| 10 | + */ | |
| 11 | +export const toolDescribed = ( | |
| 12 | + tool: string, | |
| 13 | + provider: Origin, | |
| 14 | +): ToolDescribeInput => ({ tool, description: 'd', provider }) |
| @@ -1,0 +1,12 @@ | ||
| 1 | +import type { Origin } from 'claude-code' | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Providers whose subjects a plugin the person installed may still | |
| 5 | + * rewrite: a person's plugin or MCP server, a bundled plugin, the engine. | |
| 6 | + */ | |
| 7 | +export const USER_REACHABLE_PROVIDERS: readonly Origin[] = [ | |
| 8 | + { plugin: 'mine@market', tier: 'user' }, | |
| 9 | + { plugin: 'mcp:mine', tier: 'user' }, | |
| 10 | + { plugin: 'bundled@builtin', tier: 'builtin' }, | |
| 11 | + { plugin: 'engine', tier: 'core' }, | |
| 12 | +] |
| @@ -1,0 +1,72 @@ | ||
| 1 | +import type { Settings } from 'claude-code' | |
| 2 | +import { describe, expect, test, tier } from 'claude-code/testing' | |
| 3 | + | |
| 4 | +import Policy from '../../hooks/policy' | |
| 5 | +import Fixtures from '../fixtures' | |
| 6 | + | |
| 7 | +tier('prepend') | |
| 8 | + | |
| 9 | +describe('create-policy-memo', () => { | |
| 10 | + function countedRead() { | |
| 11 | + let reads = 0 | |
| 12 | + | |
| 13 | + function read(): Promise<Settings> { | |
| 14 | + reads += 1 | |
| 15 | + | |
| 16 | + return Promise.resolve(Fixtures.MANAGED_POLICY) | |
| 17 | + } | |
| 18 | + | |
| 19 | + return { read, reads: () => reads } | |
| 20 | + } | |
| 21 | + | |
| 22 | + test('a burst of reads inside the window is one read', async () => { | |
| 23 | + let at = 0 | |
| 24 | + | |
| 25 | + const memo = Policy.createPolicyMemo(Policy.POLICY_MEMO_MS, () => at) | |
| 26 | + const { read, reads } = countedRead() | |
| 27 | + const burst = await Promise.all([memo(read), memo(read), memo(read)]) | |
| 28 | + | |
| 29 | + at += Policy.POLICY_MEMO_MS | |
| 30 | + | |
| 31 | + await memo(read) | |
| 32 | + | |
| 33 | + expect(burst).toEqual([ | |
| 34 | + Fixtures.MANAGED_POLICY, | |
| 35 | + Fixtures.MANAGED_POLICY, | |
| 36 | + Fixtures.MANAGED_POLICY, | |
| 37 | + ]) | |
| 38 | + | |
| 39 | + expect(reads()).toBe(1) | |
| 40 | + }) | |
| 41 | + | |
| 42 | + test('past the window the next decision reads again', async () => { | |
| 43 | + let at = 0 | |
| 44 | + | |
| 45 | + const memo = Policy.createPolicyMemo(Policy.POLICY_MEMO_MS, () => at) | |
| 46 | + const { read, reads } = countedRead() | |
| 47 | + | |
| 48 | + await memo(read) | |
| 49 | + | |
| 50 | + at += Policy.POLICY_MEMO_MS + 1 | |
| 51 | + | |
| 52 | + await memo(read) | |
| 53 | + | |
| 54 | + expect(reads()).toBe(2) | |
| 55 | + }) | |
| 56 | + | |
| 57 | + test('a failed read is served for its window: fails closed', async () => { | |
| 58 | + const memo = Policy.createPolicyMemo(Policy.POLICY_MEMO_MS, () => 0) | |
| 59 | + | |
| 60 | + let reads = 0 | |
| 61 | + | |
| 62 | + function failing(): Promise<Settings> { | |
| 63 | + reads += 1 | |
| 64 | + | |
| 65 | + return Promise.reject(new Error('settings unreadable')) | |
| 66 | + } | |
| 67 | + | |
| 68 | + await expect(memo(failing)).rejects.toThrow('settings unreadable') | |
| 69 | + await expect(memo(failing)).rejects.toThrow('settings unreadable') | |
| 70 | + expect(reads).toBe(1) | |
| 71 | + }) | |
| 72 | +}) |
| @@ -1,5 +1,6 @@ | ||
| 1 | 1 | import { describe, expect, test, tier } from 'claude-code/testing' |
| 2 | 2 | |
| 3 | +import Hooks from '../hooks' | |
| 3 | 4 | import Fixtures from './fixtures' |
| 4 | 5 | |
| 5 | 6 | tier('prepend') |
| @@ -9,6 +10,7 @@ | ||
| 9 | 10 | 'under an MCP allowlist a plugin the person installed may not add a tool', |
| 10 | 11 | { |
| 11 | 12 | plugins: [ |
| 13 | + Fixtures.registering('suite', 'prepend'), | |
| 12 | 14 | Fixtures.registering('mine'), |
| 13 | 15 | Fixtures.registering('bundled', 'builtin'), |
| 14 | 16 | ], |
| @@ -20,7 +22,7 @@ | ||
| 20 | 22 | |
| 21 | 23 | await $.session.start(Fixtures.SESSION) |
| 22 | 24 | |
| 23 | - expect(registered).toEqual(['bundled']) | |
| 25 | + expect(registered).toEqual(['suite', 'bundled']) | |
| 24 | 26 | }, |
| 25 | 27 | ) |
| 26 | 28 | |
| @@ -39,6 +41,29 @@ | ||
| 39 | 41 | ) |
| 40 | 42 | |
| 41 | 43 | test( |
| 44 | + "an organization's registration passes over a refusing user plugin", | |
| 45 | + { | |
| 46 | + plugins: [ | |
| 47 | + Fixtures.registering('suite', 'prepend'), | |
| 48 | + Fixtures.denying, | |
| 49 | + Fixtures.registering('bundled', 'builtin'), | |
| 50 | + ], | |
| 51 | + }, | |
| 52 | + async ($, on) => { | |
| 53 | + on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) | |
| 54 | + | |
| 55 | + const registered = Fixtures.toolsRegistered(on) | |
| 56 | + | |
| 57 | + await $.session.start(Fixtures.SESSION) | |
| 58 | + | |
| 59 | + expect( | |
| 60 | + registered, | |
| 61 | + "a built-in's registration still meets the user's deny", | |
| 62 | + ).toEqual(['suite']) | |
| 63 | + }, | |
| 64 | + ) | |
| 65 | + | |
| 66 | + test( | |
| 42 | 67 | 'a policy that cannot be read counts as one in force', |
| 43 | 68 | { plugins: [Fixtures.registering('mine')] }, |
| 44 | 69 | async ($, on) => { |
| @@ -69,6 +94,38 @@ | ||
| 69 | 94 | ) |
| 70 | 95 | |
| 71 | 96 | test( |
| 97 | + 'a server policy delivers itself counts as a tool policy, unlisted', | |
| 98 | + { plugins: [Fixtures.relabeling, Fixtures.listing] }, | |
| 99 | + async ($, on) => { | |
| 100 | + on('settings.read', () => ({ value: Fixtures.SERVER_POLICY })) | |
| 101 | + on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) | |
| 102 | + | |
| 103 | + const { text } = await $.command.run(Fixtures.TOOLS_COMMAND) | |
| 104 | + | |
| 105 | + expect(text?.split('\n')).toEqual([ | |
| 106 | + 'mcp__corp__search: Searches the corp wiki.', | |
| 107 | + 'Bash: relabeled', | |
| 108 | + ]) | |
| 109 | + }, | |
| 110 | + ) | |
| 111 | + | |
| 112 | + test( | |
| 113 | + "with no policy to read the organization's listing stands whole", | |
| 114 | + { plugins: [Fixtures.relabeling, Fixtures.listing] }, | |
| 115 | + async ($, on) => { | |
| 116 | + on('settings.read', () => ({ deny: 'settings unreadable' })) | |
| 117 | + on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) | |
| 118 | + | |
| 119 | + const { text } = await $.command.run(Fixtures.TOOLS_COMMAND) | |
| 120 | + | |
| 121 | + expect(text?.split('\n')).toEqual([ | |
| 122 | + 'mcp__corp__search: Searches the corp wiki.', | |
| 123 | + 'Bash: Runs a command.', | |
| 124 | + ]) | |
| 125 | + }, | |
| 126 | + ) | |
| 127 | + | |
| 128 | + test( | |
| 72 | 129 | 'a prompt section passes over the plugins the person installed', |
| 73 | 130 | { plugins: [Fixtures.dropping, Fixtures.signing] }, |
| 74 | 131 | async ($, on) => { |
| @@ -79,4 +136,173 @@ | ||
| 79 | 136 | }) |
| 80 | 137 | }, |
| 81 | 138 | ) |
| 139 | + | |
| 140 | + test( | |
| 141 | + "a user plugin's rewrite of policy is skipped for every other reader", | |
| 142 | + { | |
| 143 | + plugins: [ | |
| 144 | + Fixtures.stripping, | |
| 145 | + Fixtures.reading, | |
| 146 | + Fixtures.registering('mine'), | |
| 147 | + ], | |
| 148 | + }, | |
| 149 | + async ($, on) => { | |
| 150 | + on('settings.read', () => ({ value: Fixtures.MANAGED_POLICY })) | |
| 151 | + | |
| 152 | + const registered = Fixtures.toolsRegistered(on) | |
| 153 | + const { text } = await $.command.run(Fixtures.POLICY_COMMAND) | |
| 154 | + | |
| 155 | + await $.session.start(Fixtures.SESSION) | |
| 156 | + | |
| 157 | + expect( | |
| 158 | + JSON.parse(text ?? 'null'), | |
| 159 | + "another user plugin's read sees the allowlist the stripper hides", | |
| 160 | + ).toEqual(Fixtures.MANAGED_POLICY) | |
| 161 | + | |
| 162 | + expect( | |
| 163 | + registered, | |
| 164 | + "sec-default's own tool.register hook still reads the allowlist", | |
| 165 | + ).toEqual([]) | |
| 166 | + }, | |
| 167 | + ) | |
| 168 | + | |
| 169 | + test( | |
| 170 | + "an organization provider's subject passes over the user plugins", | |
| 171 | + { plugins: [Fixtures.marking] }, | |
| 172 | + async ($, on) => { | |
| 173 | + Fixtures.subjectsEchoed(on) | |
| 174 | + | |
| 175 | + for (const provider of Fixtures.ORG_PROVIDERS) { | |
| 176 | + expect( | |
| 177 | + await $.tool.describe( | |
| 178 | + Fixtures.toolDescribed('mcp__corp__search', provider), | |
| 179 | + ), | |
| 180 | + ).toEqual({ description: 'd' }) | |
| 181 | + | |
| 182 | + expect( | |
| 183 | + ( | |
| 184 | + await $.command.describe( | |
| 185 | + Fixtures.commandDescribed('suite:deploy', provider), | |
| 186 | + ) | |
| 187 | + ).isHidden, | |
| 188 | + ).toBe(false) | |
| 189 | + | |
| 190 | + expect( | |
| 191 | + await $.agent.offer( | |
| 192 | + Fixtures.agentOffered('suite:reviewer', provider), | |
| 193 | + ), | |
| 194 | + ).toEqual({ isOffered: true }) | |
| 195 | + | |
| 196 | + expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ | |
| 197 | + model: 'core', | |
| 198 | + }) | |
| 199 | + } | |
| 200 | + | |
| 201 | + for (const provider of Fixtures.USER_REACHABLE_PROVIDERS) { | |
| 202 | + expect( | |
| 203 | + await $.tool.describe( | |
| 204 | + Fixtures.toolDescribed('mcp__mine__search', provider), | |
| 205 | + ), | |
| 206 | + ).toEqual({ description: 'user: d' }) | |
| 207 | + | |
| 208 | + expect( | |
| 209 | + ( | |
| 210 | + await $.command.describe( | |
| 211 | + Fixtures.commandDescribed('mine:deploy', provider), | |
| 212 | + ) | |
| 213 | + ).isHidden, | |
| 214 | + ).toBe(true) | |
| 215 | + | |
| 216 | + expect( | |
| 217 | + await $.agent.offer(Fixtures.agentOffered('reviewer', provider)), | |
| 218 | + ).toEqual({ isOffered: false }) | |
| 219 | + | |
| 220 | + expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ | |
| 221 | + model: 'user', | |
| 222 | + }) | |
| 223 | + } | |
| 224 | + }, | |
| 225 | + ) | |
| 226 | + | |
| 227 | + test( | |
| 228 | + 'an odd provider passes over the user plugins: it fails closed', | |
| 229 | + { plugins: [Fixtures.marking] }, | |
| 230 | + async ($, on) => { | |
| 231 | + Fixtures.subjectsEchoed(on) | |
| 232 | + | |
| 233 | + for (const provider of Fixtures.ODD_PROVIDERS) { | |
| 234 | + expect( | |
| 235 | + await $.tool.describe(Fixtures.toolDescribed('Bash', provider)), | |
| 236 | + ).toEqual({ description: 'd' }) | |
| 237 | + | |
| 238 | + expect(await $.agent.spawn(Fixtures.agentSpawned(provider))).toEqual({ | |
| 239 | + model: 'core', | |
| 240 | + }) | |
| 241 | + } | |
| 242 | + }, | |
| 243 | + ) | |
| 244 | + | |
| 245 | + test( | |
| 246 | + 'a subject decision reads no policy; a burst of listings reads once', | |
| 247 | + { plugins: [Fixtures.listing] }, | |
| 248 | + async ($, on) => { | |
| 249 | + const reads = Fixtures.policyReads(on, Fixtures.MANAGED_POLICY) | |
| 250 | + | |
| 251 | + Fixtures.subjectsEchoed(on) | |
| 252 | + on('tool.list', () => ({ value: [...Fixtures.TOOLS] })) | |
| 253 | + | |
| 254 | + for (const provider of Fixtures.ORG_PROVIDERS) { | |
| 255 | + await $.tool.describe(Fixtures.toolDescribed('mcp__corp__x', provider)) | |
| 256 | + await $.agent.spawn(Fixtures.agentSpawned(provider)) | |
| 257 | + } | |
| 258 | + | |
| 259 | + expect(reads()).toBe(0) | |
| 260 | + | |
| 261 | + await Promise.all([ | |
| 262 | + $.command.run(Fixtures.TOOLS_COMMAND), | |
| 263 | + $.command.run(Fixtures.TOOLS_COMMAND), | |
| 264 | + $.command.run(Fixtures.TOOLS_COMMAND), | |
| 265 | + ]) | |
| 266 | + | |
| 267 | + expect(reads()).toBe(1) | |
| 268 | + }, | |
| 269 | + ) | |
| 270 | + | |
| 271 | + test( | |
| 272 | + 'the refusal a user-tier caller reads names the allowlist', | |
| 273 | + { | |
| 274 | + plugins: [ | |
| 275 | + { | |
| 276 | + name: 'asking', | |
| 277 | + register(on) { | |
| 278 | + on('command.run', { command: 'greet' }, $ => | |
| 279 | + $.tool | |
| 280 | + .register({ | |
| 281 | + name: 'greet', | |
| 282 | + description: 'Says hello.', | |
| 283 | + inputSchema: { type: 'object' }, | |
| 284 | + }) | |
| 285 | + .then( | |
| 286 | + () => ({ text: 'registered' }), | |
| 287 | + (error: unknown) => ({ text: String(error) }), | |
| 288 | + ), | |
| 289 | + ) | |
| 290 | + }, | |
| 291 | + }, | |
| 292 | + ], | |
| 293 | + }, | |
| 294 | + async ($, on) => { | |
| 295 | + on('settings.read', () => ({ value: Fixtures.ALLOWLIST })) | |
| 296 | + | |
| 297 | + const { text } = await $.command.run({ | |
| 298 | + command: 'greet', | |
| 299 | + args: '', | |
| 300 | + origin: { kind: 'composer' }, | |
| 301 | + }) | |
| 302 | + | |
| 303 | + expect(text).toEndWith( | |
| 304 | + `asking: $.tool.register: ${Hooks.TOOL_REGISTER_REFUSAL}`, | |
| 305 | + ) | |
| 306 | + }, | |
| 307 | + ) | |
| 82 | 308 | }) |