mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
feat: 恢复 --channels 能力 (#297)
* feat: 恢复 --channels 能力 * docs: 添加 channels 注释
This commit is contained in:
@@ -12,50 +12,27 @@ import {
|
||||
getHasDevChannels,
|
||||
} from '../../bootstrap/state.js'
|
||||
import { Box, Text } from '@anthropic/ink'
|
||||
import { isChannelsEnabled } from '../../services/mcp/channelAllowlist.js'
|
||||
import { getEffectiveChannelAllowlist } from '../../services/mcp/channelNotification.js'
|
||||
import { getMcpConfigsByScope } from '../../services/mcp/config.js'
|
||||
import {
|
||||
getClaudeAIOAuthTokens,
|
||||
getSubscriptionType,
|
||||
} from '../../utils/auth.js'
|
||||
import { loadInstalledPluginsV2 } from '../../utils/plugins/installedPluginsManager.js'
|
||||
import { getSettingsForSource } from '../../utils/settings/settings.js'
|
||||
|
||||
export function ChannelsNotice(): React.ReactNode {
|
||||
// Snapshot all reads at mount. This notice enters scrollback immediately
|
||||
// after the logo; any re-render past that point forces a full terminal
|
||||
// reset. getAllowedChannels (bootstrap state), getSettingsForSource
|
||||
// (session cache updated by background polling / /login), and
|
||||
// isChannelsEnabled (GrowthBook 5-min refresh) must be captured once
|
||||
// so a later re-render cannot flip branches.
|
||||
const [{ channels, disabled, noAuth, policyBlocked, list, unmatched }] =
|
||||
// reset.
|
||||
const [{ channels, list, unmatched }] =
|
||||
useState(() => {
|
||||
const ch = getAllowedChannels()
|
||||
if (ch.length === 0)
|
||||
return {
|
||||
channels: ch,
|
||||
disabled: false,
|
||||
noAuth: false,
|
||||
policyBlocked: false,
|
||||
list: '',
|
||||
unmatched: [] as Unmatched[],
|
||||
}
|
||||
const l = ch.map(formatEntry).join(', ')
|
||||
const sub = getSubscriptionType()
|
||||
const managed = sub === 'team' || sub === 'enterprise'
|
||||
const policy = getSettingsForSource('policySettings')
|
||||
const allowlist = getEffectiveChannelAllowlist(
|
||||
sub,
|
||||
policy?.allowedChannelPlugins,
|
||||
)
|
||||
return {
|
||||
channels: ch,
|
||||
disabled: !isChannelsEnabled(),
|
||||
noAuth: !getClaudeAIOAuthTokens()?.accessToken,
|
||||
policyBlocked: managed && policy?.channelsEnabled !== true,
|
||||
list: l,
|
||||
unmatched: findUnmatched(ch, allowlist),
|
||||
unmatched: findUnmatched(ch),
|
||||
}
|
||||
})
|
||||
if (channels.length === 0) return null
|
||||
@@ -70,50 +47,6 @@ export function ChannelsNotice(): React.ReactNode {
|
||||
? '--dangerously-load-development-channels'
|
||||
: '--channels'
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text color="error">
|
||||
{flag} ignored ({list})
|
||||
</Text>
|
||||
<Text dimColor>Channels are not currently available</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
if (noAuth) {
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text color="error">
|
||||
{flag} ignored ({list})
|
||||
</Text>
|
||||
<Text dimColor>
|
||||
Channels require claude.ai authentication · run /login, then restart
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
if (policyBlocked) {
|
||||
return (
|
||||
<Box paddingLeft={2} flexDirection="column">
|
||||
<Text color="error">
|
||||
{flag} blocked by org policy ({list})
|
||||
</Text>
|
||||
<Text dimColor>Inbound messages will be silently dropped</Text>
|
||||
<Text dimColor>
|
||||
Have an administrator set channelsEnabled: true in managed settings to
|
||||
enable
|
||||
</Text>
|
||||
{unmatched.map(u => (
|
||||
<Text key={`${formatEntry(u.entry)}:${u.why}`} color="warning">
|
||||
{formatEntry(u.entry)} · {u.why}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
// "Listening for" not "active" — at this point we only know the allowlist
|
||||
// was set. Server connection, capability declaration, and whether the name
|
||||
// even matches a configured MCP server are all still unknown.
|
||||
@@ -144,7 +77,6 @@ type Unmatched = { entry: ChannelEntry; why: string }
|
||||
|
||||
function findUnmatched(
|
||||
entries: readonly ChannelEntry[],
|
||||
allowlist: ReturnType<typeof getEffectiveChannelAllowlist>,
|
||||
): Unmatched[] {
|
||||
// Server-kind: build one Set from all scopes up front. getMcpConfigsByScope
|
||||
// is not cached (project scope walks the dir tree); getMcpConfigByName would
|
||||
@@ -163,46 +95,17 @@ function findUnmatched(
|
||||
Object.keys(loadInstalledPluginsV2().plugins),
|
||||
)
|
||||
|
||||
// Plugin-kind allowlist check: same {marketplace, plugin} test as the
|
||||
// gate at channelNotification.ts. entry.dev bypasses (dev flag opts out
|
||||
// of the allowlist). Org list replaces ledger when set (team/enterprise).
|
||||
// GrowthBook _CACHED_MAY_BE_STALE — cold cache yields [] so every plugin
|
||||
// entry warns; same tradeoff the gate already accepts.
|
||||
const { entries: allowed, source } = allowlist
|
||||
|
||||
// Independent ifs — a plugin entry that's both uninstalled AND
|
||||
// unlisted shows two lines. Server kind checks config + dev flag.
|
||||
const out: Unmatched[] = []
|
||||
for (const entry of entries) {
|
||||
if (entry.kind === 'server') {
|
||||
if (!configured.has(entry.name)) {
|
||||
out.push({ entry, why: 'no MCP server configured with that name' })
|
||||
}
|
||||
if (!entry.dev) {
|
||||
out.push({
|
||||
entry,
|
||||
why: 'server: entries need --dangerously-load-development-channels',
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (!installedPluginIds.has(`${entry.name}@${entry.marketplace}`)) {
|
||||
out.push({ entry, why: 'plugin not installed' })
|
||||
}
|
||||
if (
|
||||
!entry.dev &&
|
||||
!allowed.some(
|
||||
e => e.plugin === entry.name && e.marketplace === entry.marketplace,
|
||||
)
|
||||
) {
|
||||
out.push({
|
||||
entry,
|
||||
why:
|
||||
source === 'org'
|
||||
? "not on your org's approved channels list"
|
||||
: 'not on the approved channels allowlist',
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user