init claude-code
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
|
||||
import type { UUID } from 'crypto'
|
||||
import type { CanUseToolFn } from '../hooks/useCanUseTool.js'
|
||||
import type { CompactionResult } from '../services/compact/compact.js'
|
||||
import type { ScopedMcpServerConfig } from '../services/mcp/types.js'
|
||||
import type { ToolUseContext } from '../Tool.js'
|
||||
import type { EffortValue } from '../utils/effort.js'
|
||||
import type { IDEExtensionInstallationStatus, IdeType } from '../utils/ide.js'
|
||||
import type { SettingSource } from '../utils/settings/constants.js'
|
||||
import type { HooksSettings } from '../utils/settings/types.js'
|
||||
import type { ThemeName } from '../utils/theme.js'
|
||||
import type { LogOption } from './logs.js'
|
||||
import type { Message } from './message.js'
|
||||
import type { PluginManifest } from './plugin.js'
|
||||
|
||||
export type LocalCommandResult =
|
||||
| { type: 'text'; value: string }
|
||||
| {
|
||||
type: 'compact'
|
||||
compactionResult: CompactionResult
|
||||
displayText?: string
|
||||
}
|
||||
| { type: 'skip' } // Skip messages
|
||||
|
||||
export type PromptCommand = {
|
||||
type: 'prompt'
|
||||
progressMessage: string
|
||||
contentLength: number // Length of command content in characters (used for token estimation)
|
||||
argNames?: string[]
|
||||
allowedTools?: string[]
|
||||
model?: string
|
||||
source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
|
||||
pluginInfo?: {
|
||||
pluginManifest: PluginManifest
|
||||
repository: string
|
||||
}
|
||||
disableNonInteractive?: boolean
|
||||
// Hooks to register when this skill is invoked
|
||||
hooks?: HooksSettings
|
||||
// Base directory for skill resources (used to set CLAUDE_PLUGIN_ROOT environment variable for skill hooks)
|
||||
skillRoot?: string
|
||||
// Execution context: 'inline' (default) or 'fork' (run as sub-agent)
|
||||
// 'inline' = skill content expands into the current conversation
|
||||
// 'fork' = skill runs in a sub-agent with separate context and token budget
|
||||
context?: 'inline' | 'fork'
|
||||
// Agent type to use when forked (e.g., 'Bash', 'general-purpose')
|
||||
// Only applicable when context is 'fork'
|
||||
agent?: string
|
||||
effort?: EffortValue
|
||||
// Glob patterns for file paths this skill applies to
|
||||
// When set, the skill is only visible after the model touches matching files
|
||||
paths?: string[]
|
||||
getPromptForCommand(
|
||||
args: string,
|
||||
context: ToolUseContext,
|
||||
): Promise<ContentBlockParam[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* The call signature for a local command implementation.
|
||||
*/
|
||||
export type LocalCommandCall = (
|
||||
args: string,
|
||||
context: LocalJSXCommandContext,
|
||||
) => Promise<LocalCommandResult>
|
||||
|
||||
/**
|
||||
* Module shape returned by load() for lazy-loaded local commands.
|
||||
*/
|
||||
export type LocalCommandModule = {
|
||||
call: LocalCommandCall
|
||||
}
|
||||
|
||||
type LocalCommand = {
|
||||
type: 'local'
|
||||
supportsNonInteractive: boolean
|
||||
load: () => Promise<LocalCommandModule>
|
||||
}
|
||||
|
||||
export type LocalJSXCommandContext = ToolUseContext & {
|
||||
canUseTool?: CanUseToolFn
|
||||
setMessages: (updater: (prev: Message[]) => Message[]) => void
|
||||
options: {
|
||||
dynamicMcpConfig?: Record<string, ScopedMcpServerConfig>
|
||||
ideInstallationStatus: IDEExtensionInstallationStatus | null
|
||||
theme: ThemeName
|
||||
}
|
||||
onChangeAPIKey: () => void
|
||||
onChangeDynamicMcpConfig?: (
|
||||
config: Record<string, ScopedMcpServerConfig>,
|
||||
) => void
|
||||
onInstallIDEExtension?: (ide: IdeType) => void
|
||||
resume?: (
|
||||
sessionId: UUID,
|
||||
log: LogOption,
|
||||
entrypoint: ResumeEntrypoint,
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
export type ResumeEntrypoint =
|
||||
| 'cli_flag'
|
||||
| 'slash_command_picker'
|
||||
| 'slash_command_session_id'
|
||||
| 'slash_command_title'
|
||||
| 'fork'
|
||||
|
||||
export type CommandResultDisplay = 'skip' | 'system' | 'user'
|
||||
|
||||
/**
|
||||
* Callback when a command completes.
|
||||
* @param result - Optional user-visible message to display
|
||||
* @param options - Optional configuration for command completion
|
||||
* @param options.display - How to display the result: 'skip' | 'system' | 'user' (default)
|
||||
* @param options.shouldQuery - If true, send messages to the model after command completes
|
||||
* @param options.metaMessages - Additional messages to insert as isMeta (model-visible but hidden)
|
||||
*/
|
||||
export type LocalJSXCommandOnDone = (
|
||||
result?: string,
|
||||
options?: {
|
||||
display?: CommandResultDisplay
|
||||
shouldQuery?: boolean
|
||||
metaMessages?: string[]
|
||||
nextInput?: string
|
||||
submitNextInput?: boolean
|
||||
},
|
||||
) => void
|
||||
|
||||
/**
|
||||
* The call signature for a local JSX command implementation.
|
||||
*/
|
||||
export type LocalJSXCommandCall = (
|
||||
onDone: LocalJSXCommandOnDone,
|
||||
context: ToolUseContext & LocalJSXCommandContext,
|
||||
args: string,
|
||||
) => Promise<React.ReactNode>
|
||||
|
||||
/**
|
||||
* Module shape returned by load() for lazy-loaded commands.
|
||||
*/
|
||||
export type LocalJSXCommandModule = {
|
||||
call: LocalJSXCommandCall
|
||||
}
|
||||
|
||||
type LocalJSXCommand = {
|
||||
type: 'local-jsx'
|
||||
/**
|
||||
* Lazy-load the command implementation.
|
||||
* Returns a module with a call() function.
|
||||
* This defers loading heavy dependencies until the command is invoked.
|
||||
*/
|
||||
load: () => Promise<LocalJSXCommandModule>
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares which auth/provider environments a command is available in.
|
||||
*
|
||||
* This is separate from `isEnabled()`:
|
||||
* - `availability` = who can use this (auth/provider requirement, static)
|
||||
* - `isEnabled()` = is this turned on right now (GrowthBook, platform, env vars)
|
||||
*
|
||||
* Commands without `availability` are available everywhere.
|
||||
* Commands with `availability` are only shown if the user matches at least one
|
||||
* of the listed auth types. See meetsAvailabilityRequirement() in commands.ts.
|
||||
*
|
||||
* Example: `availability: ['claude-ai', 'console']` shows the command to
|
||||
* claude.ai subscribers and direct Console API key users (api.anthropic.com),
|
||||
* but hides it from Bedrock/Vertex/Foundry users and custom base URL users.
|
||||
*/
|
||||
export type CommandAvailability =
|
||||
// claude.ai OAuth subscriber (Pro/Max/Team/Enterprise via claude.ai)
|
||||
| 'claude-ai'
|
||||
// Console API key user (direct api.anthropic.com, not via claude.ai OAuth)
|
||||
| 'console'
|
||||
|
||||
export type CommandBase = {
|
||||
availability?: CommandAvailability[]
|
||||
description: string
|
||||
hasUserSpecifiedDescription?: boolean
|
||||
/** Defaults to true. Only set when the command has conditional enablement (feature flags, env checks, etc). */
|
||||
isEnabled?: () => boolean
|
||||
/** Defaults to false. Only set when the command should be hidden from typeahead/help. */
|
||||
isHidden?: boolean
|
||||
name: string
|
||||
aliases?: string[]
|
||||
isMcp?: boolean
|
||||
argumentHint?: string // Hint text for command arguments (displayed in gray after command)
|
||||
whenToUse?: string // From the "Skill" spec. Detailed usage scenarios for when to use this command
|
||||
version?: string // Version of the command/skill
|
||||
disableModelInvocation?: boolean // Whether to disable this command from being invoked by models
|
||||
userInvocable?: boolean // Whether users can invoke this skill by typing /skill-name
|
||||
loadedFrom?:
|
||||
| 'commands_DEPRECATED'
|
||||
| 'skills'
|
||||
| 'plugin'
|
||||
| 'managed'
|
||||
| 'bundled'
|
||||
| 'mcp' // Where the command was loaded from
|
||||
kind?: 'workflow' // Distinguishes workflow-backed commands (badged in autocomplete)
|
||||
immediate?: boolean // If true, command executes immediately without waiting for a stop point (bypasses queue)
|
||||
isSensitive?: boolean // If true, args are redacted from the conversation history
|
||||
/** Defaults to `name`. Only override when the displayed name differs (e.g. plugin prefix stripping). */
|
||||
userFacingName?: () => string
|
||||
}
|
||||
|
||||
export type Command = CommandBase &
|
||||
(PromptCommand | LocalCommand | LocalJSXCommand)
|
||||
|
||||
/** Resolves the user-visible name, falling back to `cmd.name` when not overridden. */
|
||||
export function getCommandName(cmd: CommandBase): string {
|
||||
return cmd.userFacingName?.() ?? cmd.name
|
||||
}
|
||||
|
||||
/** Resolves whether the command is enabled, defaulting to true. */
|
||||
export function isCommandEnabled(cmd: CommandBase): boolean {
|
||||
return cmd.isEnabled?.() ?? true
|
||||
}
|
||||
@@ -0,0 +1,865 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.6.1
|
||||
// protoc unknown
|
||||
// source: events_mono/claude_code/v1/claude_code_internal_event.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { Timestamp } from '../../../google/protobuf/timestamp.js'
|
||||
import { PublicApiAuth } from '../../common/v1/auth.js'
|
||||
|
||||
/** GitHubActionsMetadata contains GitHub Actions-specific environment information */
|
||||
export interface GitHubActionsMetadata {
|
||||
actor_id?: string | undefined
|
||||
repository_id?: string | undefined
|
||||
repository_owner_id?: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* EnvironmentMetadata contains environment and runtime information
|
||||
* See claude-cli-internal/src/services/statsig.ts for the source of these fields
|
||||
*/
|
||||
export interface EnvironmentMetadata {
|
||||
platform?: string | undefined
|
||||
node_version?: string | undefined
|
||||
terminal?: string | undefined
|
||||
package_managers?: string | undefined
|
||||
runtimes?: string | undefined
|
||||
is_running_with_bun?: boolean | undefined
|
||||
is_ci?: boolean | undefined
|
||||
is_claubbit?: boolean | undefined
|
||||
is_github_action?: boolean | undefined
|
||||
is_claude_code_action?: boolean | undefined
|
||||
is_claude_ai_auth?: boolean | undefined
|
||||
version?: string | undefined
|
||||
/** GitHub Actions specific fields (only present when is_github_action is true) */
|
||||
github_event_name?: string | undefined
|
||||
github_actions_runner_environment?: string | undefined
|
||||
github_actions_runner_os?: string | undefined
|
||||
github_action_ref?: string | undefined
|
||||
/** WSL specific field */
|
||||
wsl_version?: string | undefined
|
||||
/** GitHub metadata (only present when is_github_action is true) */
|
||||
github_actions_metadata?: GitHubActionsMetadata | undefined
|
||||
arch?: string | undefined
|
||||
is_claude_code_remote?: boolean | undefined
|
||||
remote_environment_type?: string | undefined
|
||||
claude_code_container_id?: string | undefined
|
||||
claude_code_remote_session_id?: string | undefined
|
||||
tags?: string[] | undefined
|
||||
deployment_environment?: string | undefined
|
||||
is_conductor?: boolean | undefined
|
||||
version_base?: string | undefined
|
||||
coworker_type?: string | undefined
|
||||
build_time?: string | undefined
|
||||
is_local_agent_mode?: boolean | undefined
|
||||
linux_distro_id?: string | undefined
|
||||
linux_distro_version?: string | undefined
|
||||
linux_kernel?: string | undefined
|
||||
vcs?: string | undefined
|
||||
platform_raw?: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* SlackContext contains context fields present on every Claude-in-Slack (CIS) event.
|
||||
* Event-specific fields (errorType, durationMs, httpStatus, etc.) go in
|
||||
* ClaudeCodeInternalEvent.additional_metadata as JSON.
|
||||
*/
|
||||
export interface SlackContext {
|
||||
slack_team_id?: string | undefined
|
||||
is_enterprise_install?: boolean | undefined
|
||||
trigger?: string | undefined
|
||||
creation_method?: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* ClaudeCodeInternalEvent represents events logged from Claude Code via Statsig
|
||||
* This schema matches the structure in claude-cli-internal/src/services/statsig.ts
|
||||
* Source table: proj-product-data-nhme.raw_statsig_internal_tools.events
|
||||
*/
|
||||
export interface ClaudeCodeInternalEvent {
|
||||
/** Event name (e.g., "tengu_binary_feedback", "tengu_api_success") */
|
||||
event_name?: string | undefined
|
||||
/** Event timestamp */
|
||||
client_timestamp?: Date | undefined
|
||||
model?: string | undefined
|
||||
session_id?: string | undefined
|
||||
user_type?: string | undefined
|
||||
betas?: string | undefined
|
||||
/** Environment and runtime information */
|
||||
env?: EnvironmentMetadata | undefined
|
||||
entrypoint?: string | undefined
|
||||
agent_sdk_version?: string | undefined
|
||||
is_interactive?: boolean | undefined
|
||||
client_type?: string | undefined
|
||||
/**
|
||||
* Process metrics as JSON string (ant-only)
|
||||
* Contains: uptime, rss, heapTotal, heapUsed, external, arrayBuffers,
|
||||
* constrainedMemory, cpuUsage
|
||||
*/
|
||||
process?: string | undefined
|
||||
/**
|
||||
* Additional metadata passed to logEvent (event-specific)
|
||||
* This includes fields like msg_id_A, msg_id_B, gitBranch, gitHead, etc.
|
||||
* that vary per event type
|
||||
*/
|
||||
additional_metadata?: string | undefined
|
||||
/** Authentication context automatically injected by the API */
|
||||
auth?: PublicApiAuth | undefined
|
||||
/** Server timestamp automatically injected by the API */
|
||||
server_timestamp?: Date | undefined
|
||||
/** Unique identifier for this event (automatically generated by API endpoint) */
|
||||
event_id?: string | undefined
|
||||
/** Device identifier for the client */
|
||||
device_id?: string | undefined
|
||||
/** SWE-bench fields */
|
||||
swe_bench_run_id?: string | undefined
|
||||
swe_bench_instance_id?: string | undefined
|
||||
swe_bench_task_id?: string | undefined
|
||||
email?: string | undefined
|
||||
/** Swarm/team agent identification for analytics attribution */
|
||||
agent_id?: string | undefined
|
||||
parent_session_id?: string | undefined
|
||||
agent_type?: string | undefined
|
||||
/** Claude-in-Slack context (only present for cis_* events) */
|
||||
slack?: SlackContext | undefined
|
||||
team_name?: string | undefined
|
||||
skill_name?: string | undefined
|
||||
plugin_name?: string | undefined
|
||||
marketplace_name?: string | undefined
|
||||
}
|
||||
|
||||
function createBaseGitHubActionsMetadata(): GitHubActionsMetadata {
|
||||
return { actor_id: '', repository_id: '', repository_owner_id: '' }
|
||||
}
|
||||
|
||||
export const GitHubActionsMetadata: MessageFns<GitHubActionsMetadata> = {
|
||||
fromJSON(object: any): GitHubActionsMetadata {
|
||||
return {
|
||||
actor_id: isSet(object.actor_id)
|
||||
? globalThis.String(object.actor_id)
|
||||
: '',
|
||||
repository_id: isSet(object.repository_id)
|
||||
? globalThis.String(object.repository_id)
|
||||
: '',
|
||||
repository_owner_id: isSet(object.repository_owner_id)
|
||||
? globalThis.String(object.repository_owner_id)
|
||||
: '',
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: GitHubActionsMetadata): unknown {
|
||||
const obj: any = {}
|
||||
if (message.actor_id !== undefined) {
|
||||
obj.actor_id = message.actor_id
|
||||
}
|
||||
if (message.repository_id !== undefined) {
|
||||
obj.repository_id = message.repository_id
|
||||
}
|
||||
if (message.repository_owner_id !== undefined) {
|
||||
obj.repository_owner_id = message.repository_owner_id
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<GitHubActionsMetadata>, I>>(
|
||||
base?: I,
|
||||
): GitHubActionsMetadata {
|
||||
return GitHubActionsMetadata.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<GitHubActionsMetadata>, I>>(
|
||||
object: I,
|
||||
): GitHubActionsMetadata {
|
||||
const message = createBaseGitHubActionsMetadata()
|
||||
message.actor_id = object.actor_id ?? ''
|
||||
message.repository_id = object.repository_id ?? ''
|
||||
message.repository_owner_id = object.repository_owner_id ?? ''
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseEnvironmentMetadata(): EnvironmentMetadata {
|
||||
return {
|
||||
platform: '',
|
||||
node_version: '',
|
||||
terminal: '',
|
||||
package_managers: '',
|
||||
runtimes: '',
|
||||
is_running_with_bun: false,
|
||||
is_ci: false,
|
||||
is_claubbit: false,
|
||||
is_github_action: false,
|
||||
is_claude_code_action: false,
|
||||
is_claude_ai_auth: false,
|
||||
version: '',
|
||||
github_event_name: '',
|
||||
github_actions_runner_environment: '',
|
||||
github_actions_runner_os: '',
|
||||
github_action_ref: '',
|
||||
wsl_version: '',
|
||||
github_actions_metadata: undefined,
|
||||
arch: '',
|
||||
is_claude_code_remote: false,
|
||||
remote_environment_type: '',
|
||||
claude_code_container_id: '',
|
||||
claude_code_remote_session_id: '',
|
||||
tags: [],
|
||||
deployment_environment: '',
|
||||
is_conductor: false,
|
||||
version_base: '',
|
||||
coworker_type: '',
|
||||
build_time: '',
|
||||
is_local_agent_mode: false,
|
||||
linux_distro_id: '',
|
||||
linux_distro_version: '',
|
||||
linux_kernel: '',
|
||||
vcs: '',
|
||||
platform_raw: '',
|
||||
}
|
||||
}
|
||||
|
||||
export const EnvironmentMetadata: MessageFns<EnvironmentMetadata> = {
|
||||
fromJSON(object: any): EnvironmentMetadata {
|
||||
return {
|
||||
platform: isSet(object.platform)
|
||||
? globalThis.String(object.platform)
|
||||
: '',
|
||||
node_version: isSet(object.node_version)
|
||||
? globalThis.String(object.node_version)
|
||||
: '',
|
||||
terminal: isSet(object.terminal)
|
||||
? globalThis.String(object.terminal)
|
||||
: '',
|
||||
package_managers: isSet(object.package_managers)
|
||||
? globalThis.String(object.package_managers)
|
||||
: '',
|
||||
runtimes: isSet(object.runtimes)
|
||||
? globalThis.String(object.runtimes)
|
||||
: '',
|
||||
is_running_with_bun: isSet(object.is_running_with_bun)
|
||||
? globalThis.Boolean(object.is_running_with_bun)
|
||||
: false,
|
||||
is_ci: isSet(object.is_ci) ? globalThis.Boolean(object.is_ci) : false,
|
||||
is_claubbit: isSet(object.is_claubbit)
|
||||
? globalThis.Boolean(object.is_claubbit)
|
||||
: false,
|
||||
is_github_action: isSet(object.is_github_action)
|
||||
? globalThis.Boolean(object.is_github_action)
|
||||
: false,
|
||||
is_claude_code_action: isSet(object.is_claude_code_action)
|
||||
? globalThis.Boolean(object.is_claude_code_action)
|
||||
: false,
|
||||
is_claude_ai_auth: isSet(object.is_claude_ai_auth)
|
||||
? globalThis.Boolean(object.is_claude_ai_auth)
|
||||
: false,
|
||||
version: isSet(object.version) ? globalThis.String(object.version) : '',
|
||||
github_event_name: isSet(object.github_event_name)
|
||||
? globalThis.String(object.github_event_name)
|
||||
: '',
|
||||
github_actions_runner_environment: isSet(
|
||||
object.github_actions_runner_environment,
|
||||
)
|
||||
? globalThis.String(object.github_actions_runner_environment)
|
||||
: '',
|
||||
github_actions_runner_os: isSet(object.github_actions_runner_os)
|
||||
? globalThis.String(object.github_actions_runner_os)
|
||||
: '',
|
||||
github_action_ref: isSet(object.github_action_ref)
|
||||
? globalThis.String(object.github_action_ref)
|
||||
: '',
|
||||
wsl_version: isSet(object.wsl_version)
|
||||
? globalThis.String(object.wsl_version)
|
||||
: '',
|
||||
github_actions_metadata: isSet(object.github_actions_metadata)
|
||||
? GitHubActionsMetadata.fromJSON(object.github_actions_metadata)
|
||||
: undefined,
|
||||
arch: isSet(object.arch) ? globalThis.String(object.arch) : '',
|
||||
is_claude_code_remote: isSet(object.is_claude_code_remote)
|
||||
? globalThis.Boolean(object.is_claude_code_remote)
|
||||
: false,
|
||||
remote_environment_type: isSet(object.remote_environment_type)
|
||||
? globalThis.String(object.remote_environment_type)
|
||||
: '',
|
||||
claude_code_container_id: isSet(object.claude_code_container_id)
|
||||
? globalThis.String(object.claude_code_container_id)
|
||||
: '',
|
||||
claude_code_remote_session_id: isSet(object.claude_code_remote_session_id)
|
||||
? globalThis.String(object.claude_code_remote_session_id)
|
||||
: '',
|
||||
tags: globalThis.Array.isArray(object?.tags)
|
||||
? object.tags.map((e: any) => globalThis.String(e))
|
||||
: [],
|
||||
deployment_environment: isSet(object.deployment_environment)
|
||||
? globalThis.String(object.deployment_environment)
|
||||
: '',
|
||||
is_conductor: isSet(object.is_conductor)
|
||||
? globalThis.Boolean(object.is_conductor)
|
||||
: false,
|
||||
version_base: isSet(object.version_base)
|
||||
? globalThis.String(object.version_base)
|
||||
: '',
|
||||
coworker_type: isSet(object.coworker_type)
|
||||
? globalThis.String(object.coworker_type)
|
||||
: '',
|
||||
build_time: isSet(object.build_time)
|
||||
? globalThis.String(object.build_time)
|
||||
: '',
|
||||
is_local_agent_mode: isSet(object.is_local_agent_mode)
|
||||
? globalThis.Boolean(object.is_local_agent_mode)
|
||||
: false,
|
||||
linux_distro_id: isSet(object.linux_distro_id)
|
||||
? globalThis.String(object.linux_distro_id)
|
||||
: '',
|
||||
linux_distro_version: isSet(object.linux_distro_version)
|
||||
? globalThis.String(object.linux_distro_version)
|
||||
: '',
|
||||
linux_kernel: isSet(object.linux_kernel)
|
||||
? globalThis.String(object.linux_kernel)
|
||||
: '',
|
||||
vcs: isSet(object.vcs) ? globalThis.String(object.vcs) : '',
|
||||
platform_raw: isSet(object.platform_raw)
|
||||
? globalThis.String(object.platform_raw)
|
||||
: '',
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: EnvironmentMetadata): unknown {
|
||||
const obj: any = {}
|
||||
if (message.platform !== undefined) {
|
||||
obj.platform = message.platform
|
||||
}
|
||||
if (message.node_version !== undefined) {
|
||||
obj.node_version = message.node_version
|
||||
}
|
||||
if (message.terminal !== undefined) {
|
||||
obj.terminal = message.terminal
|
||||
}
|
||||
if (message.package_managers !== undefined) {
|
||||
obj.package_managers = message.package_managers
|
||||
}
|
||||
if (message.runtimes !== undefined) {
|
||||
obj.runtimes = message.runtimes
|
||||
}
|
||||
if (message.is_running_with_bun !== undefined) {
|
||||
obj.is_running_with_bun = message.is_running_with_bun
|
||||
}
|
||||
if (message.is_ci !== undefined) {
|
||||
obj.is_ci = message.is_ci
|
||||
}
|
||||
if (message.is_claubbit !== undefined) {
|
||||
obj.is_claubbit = message.is_claubbit
|
||||
}
|
||||
if (message.is_github_action !== undefined) {
|
||||
obj.is_github_action = message.is_github_action
|
||||
}
|
||||
if (message.is_claude_code_action !== undefined) {
|
||||
obj.is_claude_code_action = message.is_claude_code_action
|
||||
}
|
||||
if (message.is_claude_ai_auth !== undefined) {
|
||||
obj.is_claude_ai_auth = message.is_claude_ai_auth
|
||||
}
|
||||
if (message.version !== undefined) {
|
||||
obj.version = message.version
|
||||
}
|
||||
if (message.github_event_name !== undefined) {
|
||||
obj.github_event_name = message.github_event_name
|
||||
}
|
||||
if (message.github_actions_runner_environment !== undefined) {
|
||||
obj.github_actions_runner_environment =
|
||||
message.github_actions_runner_environment
|
||||
}
|
||||
if (message.github_actions_runner_os !== undefined) {
|
||||
obj.github_actions_runner_os = message.github_actions_runner_os
|
||||
}
|
||||
if (message.github_action_ref !== undefined) {
|
||||
obj.github_action_ref = message.github_action_ref
|
||||
}
|
||||
if (message.wsl_version !== undefined) {
|
||||
obj.wsl_version = message.wsl_version
|
||||
}
|
||||
if (message.github_actions_metadata !== undefined) {
|
||||
obj.github_actions_metadata = GitHubActionsMetadata.toJSON(
|
||||
message.github_actions_metadata,
|
||||
)
|
||||
}
|
||||
if (message.arch !== undefined) {
|
||||
obj.arch = message.arch
|
||||
}
|
||||
if (message.is_claude_code_remote !== undefined) {
|
||||
obj.is_claude_code_remote = message.is_claude_code_remote
|
||||
}
|
||||
if (message.remote_environment_type !== undefined) {
|
||||
obj.remote_environment_type = message.remote_environment_type
|
||||
}
|
||||
if (message.claude_code_container_id !== undefined) {
|
||||
obj.claude_code_container_id = message.claude_code_container_id
|
||||
}
|
||||
if (message.claude_code_remote_session_id !== undefined) {
|
||||
obj.claude_code_remote_session_id = message.claude_code_remote_session_id
|
||||
}
|
||||
if (message.tags?.length) {
|
||||
obj.tags = message.tags
|
||||
}
|
||||
if (message.deployment_environment !== undefined) {
|
||||
obj.deployment_environment = message.deployment_environment
|
||||
}
|
||||
if (message.is_conductor !== undefined) {
|
||||
obj.is_conductor = message.is_conductor
|
||||
}
|
||||
if (message.version_base !== undefined) {
|
||||
obj.version_base = message.version_base
|
||||
}
|
||||
if (message.coworker_type !== undefined) {
|
||||
obj.coworker_type = message.coworker_type
|
||||
}
|
||||
if (message.build_time !== undefined) {
|
||||
obj.build_time = message.build_time
|
||||
}
|
||||
if (message.is_local_agent_mode !== undefined) {
|
||||
obj.is_local_agent_mode = message.is_local_agent_mode
|
||||
}
|
||||
if (message.linux_distro_id !== undefined) {
|
||||
obj.linux_distro_id = message.linux_distro_id
|
||||
}
|
||||
if (message.linux_distro_version !== undefined) {
|
||||
obj.linux_distro_version = message.linux_distro_version
|
||||
}
|
||||
if (message.linux_kernel !== undefined) {
|
||||
obj.linux_kernel = message.linux_kernel
|
||||
}
|
||||
if (message.vcs !== undefined) {
|
||||
obj.vcs = message.vcs
|
||||
}
|
||||
if (message.platform_raw !== undefined) {
|
||||
obj.platform_raw = message.platform_raw
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<EnvironmentMetadata>, I>>(
|
||||
base?: I,
|
||||
): EnvironmentMetadata {
|
||||
return EnvironmentMetadata.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<EnvironmentMetadata>, I>>(
|
||||
object: I,
|
||||
): EnvironmentMetadata {
|
||||
const message = createBaseEnvironmentMetadata()
|
||||
message.platform = object.platform ?? ''
|
||||
message.node_version = object.node_version ?? ''
|
||||
message.terminal = object.terminal ?? ''
|
||||
message.package_managers = object.package_managers ?? ''
|
||||
message.runtimes = object.runtimes ?? ''
|
||||
message.is_running_with_bun = object.is_running_with_bun ?? false
|
||||
message.is_ci = object.is_ci ?? false
|
||||
message.is_claubbit = object.is_claubbit ?? false
|
||||
message.is_github_action = object.is_github_action ?? false
|
||||
message.is_claude_code_action = object.is_claude_code_action ?? false
|
||||
message.is_claude_ai_auth = object.is_claude_ai_auth ?? false
|
||||
message.version = object.version ?? ''
|
||||
message.github_event_name = object.github_event_name ?? ''
|
||||
message.github_actions_runner_environment =
|
||||
object.github_actions_runner_environment ?? ''
|
||||
message.github_actions_runner_os = object.github_actions_runner_os ?? ''
|
||||
message.github_action_ref = object.github_action_ref ?? ''
|
||||
message.wsl_version = object.wsl_version ?? ''
|
||||
message.github_actions_metadata =
|
||||
object.github_actions_metadata !== undefined &&
|
||||
object.github_actions_metadata !== null
|
||||
? GitHubActionsMetadata.fromPartial(object.github_actions_metadata)
|
||||
: undefined
|
||||
message.arch = object.arch ?? ''
|
||||
message.is_claude_code_remote = object.is_claude_code_remote ?? false
|
||||
message.remote_environment_type = object.remote_environment_type ?? ''
|
||||
message.claude_code_container_id = object.claude_code_container_id ?? ''
|
||||
message.claude_code_remote_session_id =
|
||||
object.claude_code_remote_session_id ?? ''
|
||||
message.tags = object.tags?.map(e => e) || []
|
||||
message.deployment_environment = object.deployment_environment ?? ''
|
||||
message.is_conductor = object.is_conductor ?? false
|
||||
message.version_base = object.version_base ?? ''
|
||||
message.coworker_type = object.coworker_type ?? ''
|
||||
message.build_time = object.build_time ?? ''
|
||||
message.is_local_agent_mode = object.is_local_agent_mode ?? false
|
||||
message.linux_distro_id = object.linux_distro_id ?? ''
|
||||
message.linux_distro_version = object.linux_distro_version ?? ''
|
||||
message.linux_kernel = object.linux_kernel ?? ''
|
||||
message.vcs = object.vcs ?? ''
|
||||
message.platform_raw = object.platform_raw ?? ''
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseSlackContext(): SlackContext {
|
||||
return {
|
||||
slack_team_id: '',
|
||||
is_enterprise_install: false,
|
||||
trigger: '',
|
||||
creation_method: '',
|
||||
}
|
||||
}
|
||||
|
||||
export const SlackContext: MessageFns<SlackContext> = {
|
||||
fromJSON(object: any): SlackContext {
|
||||
return {
|
||||
slack_team_id: isSet(object.slack_team_id)
|
||||
? globalThis.String(object.slack_team_id)
|
||||
: '',
|
||||
is_enterprise_install: isSet(object.is_enterprise_install)
|
||||
? globalThis.Boolean(object.is_enterprise_install)
|
||||
: false,
|
||||
trigger: isSet(object.trigger) ? globalThis.String(object.trigger) : '',
|
||||
creation_method: isSet(object.creation_method)
|
||||
? globalThis.String(object.creation_method)
|
||||
: '',
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: SlackContext): unknown {
|
||||
const obj: any = {}
|
||||
if (message.slack_team_id !== undefined) {
|
||||
obj.slack_team_id = message.slack_team_id
|
||||
}
|
||||
if (message.is_enterprise_install !== undefined) {
|
||||
obj.is_enterprise_install = message.is_enterprise_install
|
||||
}
|
||||
if (message.trigger !== undefined) {
|
||||
obj.trigger = message.trigger
|
||||
}
|
||||
if (message.creation_method !== undefined) {
|
||||
obj.creation_method = message.creation_method
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<SlackContext>, I>>(
|
||||
base?: I,
|
||||
): SlackContext {
|
||||
return SlackContext.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<SlackContext>, I>>(
|
||||
object: I,
|
||||
): SlackContext {
|
||||
const message = createBaseSlackContext()
|
||||
message.slack_team_id = object.slack_team_id ?? ''
|
||||
message.is_enterprise_install = object.is_enterprise_install ?? false
|
||||
message.trigger = object.trigger ?? ''
|
||||
message.creation_method = object.creation_method ?? ''
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseClaudeCodeInternalEvent(): ClaudeCodeInternalEvent {
|
||||
return {
|
||||
event_name: '',
|
||||
client_timestamp: undefined,
|
||||
model: '',
|
||||
session_id: '',
|
||||
user_type: '',
|
||||
betas: '',
|
||||
env: undefined,
|
||||
entrypoint: '',
|
||||
agent_sdk_version: '',
|
||||
is_interactive: false,
|
||||
client_type: '',
|
||||
process: '',
|
||||
additional_metadata: '',
|
||||
auth: undefined,
|
||||
server_timestamp: undefined,
|
||||
event_id: '',
|
||||
device_id: '',
|
||||
swe_bench_run_id: '',
|
||||
swe_bench_instance_id: '',
|
||||
swe_bench_task_id: '',
|
||||
email: '',
|
||||
agent_id: '',
|
||||
parent_session_id: '',
|
||||
agent_type: '',
|
||||
slack: undefined,
|
||||
team_name: '',
|
||||
skill_name: '',
|
||||
plugin_name: '',
|
||||
marketplace_name: '',
|
||||
}
|
||||
}
|
||||
|
||||
export const ClaudeCodeInternalEvent: MessageFns<ClaudeCodeInternalEvent> = {
|
||||
fromJSON(object: any): ClaudeCodeInternalEvent {
|
||||
return {
|
||||
event_name: isSet(object.event_name)
|
||||
? globalThis.String(object.event_name)
|
||||
: '',
|
||||
client_timestamp: isSet(object.client_timestamp)
|
||||
? fromJsonTimestamp(object.client_timestamp)
|
||||
: undefined,
|
||||
model: isSet(object.model) ? globalThis.String(object.model) : '',
|
||||
session_id: isSet(object.session_id)
|
||||
? globalThis.String(object.session_id)
|
||||
: '',
|
||||
user_type: isSet(object.user_type)
|
||||
? globalThis.String(object.user_type)
|
||||
: '',
|
||||
betas: isSet(object.betas) ? globalThis.String(object.betas) : '',
|
||||
env: isSet(object.env)
|
||||
? EnvironmentMetadata.fromJSON(object.env)
|
||||
: undefined,
|
||||
entrypoint: isSet(object.entrypoint)
|
||||
? globalThis.String(object.entrypoint)
|
||||
: '',
|
||||
agent_sdk_version: isSet(object.agent_sdk_version)
|
||||
? globalThis.String(object.agent_sdk_version)
|
||||
: '',
|
||||
is_interactive: isSet(object.is_interactive)
|
||||
? globalThis.Boolean(object.is_interactive)
|
||||
: false,
|
||||
client_type: isSet(object.client_type)
|
||||
? globalThis.String(object.client_type)
|
||||
: '',
|
||||
process: isSet(object.process) ? globalThis.String(object.process) : '',
|
||||
additional_metadata: isSet(object.additional_metadata)
|
||||
? globalThis.String(object.additional_metadata)
|
||||
: '',
|
||||
auth: isSet(object.auth)
|
||||
? PublicApiAuth.fromJSON(object.auth)
|
||||
: undefined,
|
||||
server_timestamp: isSet(object.server_timestamp)
|
||||
? fromJsonTimestamp(object.server_timestamp)
|
||||
: undefined,
|
||||
event_id: isSet(object.event_id)
|
||||
? globalThis.String(object.event_id)
|
||||
: '',
|
||||
device_id: isSet(object.device_id)
|
||||
? globalThis.String(object.device_id)
|
||||
: '',
|
||||
swe_bench_run_id: isSet(object.swe_bench_run_id)
|
||||
? globalThis.String(object.swe_bench_run_id)
|
||||
: '',
|
||||
swe_bench_instance_id: isSet(object.swe_bench_instance_id)
|
||||
? globalThis.String(object.swe_bench_instance_id)
|
||||
: '',
|
||||
swe_bench_task_id: isSet(object.swe_bench_task_id)
|
||||
? globalThis.String(object.swe_bench_task_id)
|
||||
: '',
|
||||
email: isSet(object.email) ? globalThis.String(object.email) : '',
|
||||
agent_id: isSet(object.agent_id)
|
||||
? globalThis.String(object.agent_id)
|
||||
: '',
|
||||
parent_session_id: isSet(object.parent_session_id)
|
||||
? globalThis.String(object.parent_session_id)
|
||||
: '',
|
||||
agent_type: isSet(object.agent_type)
|
||||
? globalThis.String(object.agent_type)
|
||||
: '',
|
||||
slack: isSet(object.slack)
|
||||
? SlackContext.fromJSON(object.slack)
|
||||
: undefined,
|
||||
team_name: isSet(object.team_name)
|
||||
? globalThis.String(object.team_name)
|
||||
: '',
|
||||
skill_name: isSet(object.skill_name)
|
||||
? globalThis.String(object.skill_name)
|
||||
: '',
|
||||
plugin_name: isSet(object.plugin_name)
|
||||
? globalThis.String(object.plugin_name)
|
||||
: '',
|
||||
marketplace_name: isSet(object.marketplace_name)
|
||||
? globalThis.String(object.marketplace_name)
|
||||
: '',
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: ClaudeCodeInternalEvent): unknown {
|
||||
const obj: any = {}
|
||||
if (message.event_name !== undefined) {
|
||||
obj.event_name = message.event_name
|
||||
}
|
||||
if (message.client_timestamp !== undefined) {
|
||||
obj.client_timestamp = message.client_timestamp.toISOString()
|
||||
}
|
||||
if (message.model !== undefined) {
|
||||
obj.model = message.model
|
||||
}
|
||||
if (message.session_id !== undefined) {
|
||||
obj.session_id = message.session_id
|
||||
}
|
||||
if (message.user_type !== undefined) {
|
||||
obj.user_type = message.user_type
|
||||
}
|
||||
if (message.betas !== undefined) {
|
||||
obj.betas = message.betas
|
||||
}
|
||||
if (message.env !== undefined) {
|
||||
obj.env = EnvironmentMetadata.toJSON(message.env)
|
||||
}
|
||||
if (message.entrypoint !== undefined) {
|
||||
obj.entrypoint = message.entrypoint
|
||||
}
|
||||
if (message.agent_sdk_version !== undefined) {
|
||||
obj.agent_sdk_version = message.agent_sdk_version
|
||||
}
|
||||
if (message.is_interactive !== undefined) {
|
||||
obj.is_interactive = message.is_interactive
|
||||
}
|
||||
if (message.client_type !== undefined) {
|
||||
obj.client_type = message.client_type
|
||||
}
|
||||
if (message.process !== undefined) {
|
||||
obj.process = message.process
|
||||
}
|
||||
if (message.additional_metadata !== undefined) {
|
||||
obj.additional_metadata = message.additional_metadata
|
||||
}
|
||||
if (message.auth !== undefined) {
|
||||
obj.auth = PublicApiAuth.toJSON(message.auth)
|
||||
}
|
||||
if (message.server_timestamp !== undefined) {
|
||||
obj.server_timestamp = message.server_timestamp.toISOString()
|
||||
}
|
||||
if (message.event_id !== undefined) {
|
||||
obj.event_id = message.event_id
|
||||
}
|
||||
if (message.device_id !== undefined) {
|
||||
obj.device_id = message.device_id
|
||||
}
|
||||
if (message.swe_bench_run_id !== undefined) {
|
||||
obj.swe_bench_run_id = message.swe_bench_run_id
|
||||
}
|
||||
if (message.swe_bench_instance_id !== undefined) {
|
||||
obj.swe_bench_instance_id = message.swe_bench_instance_id
|
||||
}
|
||||
if (message.swe_bench_task_id !== undefined) {
|
||||
obj.swe_bench_task_id = message.swe_bench_task_id
|
||||
}
|
||||
if (message.email !== undefined) {
|
||||
obj.email = message.email
|
||||
}
|
||||
if (message.agent_id !== undefined) {
|
||||
obj.agent_id = message.agent_id
|
||||
}
|
||||
if (message.parent_session_id !== undefined) {
|
||||
obj.parent_session_id = message.parent_session_id
|
||||
}
|
||||
if (message.agent_type !== undefined) {
|
||||
obj.agent_type = message.agent_type
|
||||
}
|
||||
if (message.slack !== undefined) {
|
||||
obj.slack = SlackContext.toJSON(message.slack)
|
||||
}
|
||||
if (message.team_name !== undefined) {
|
||||
obj.team_name = message.team_name
|
||||
}
|
||||
if (message.skill_name !== undefined) {
|
||||
obj.skill_name = message.skill_name
|
||||
}
|
||||
if (message.plugin_name !== undefined) {
|
||||
obj.plugin_name = message.plugin_name
|
||||
}
|
||||
if (message.marketplace_name !== undefined) {
|
||||
obj.marketplace_name = message.marketplace_name
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<ClaudeCodeInternalEvent>, I>>(
|
||||
base?: I,
|
||||
): ClaudeCodeInternalEvent {
|
||||
return ClaudeCodeInternalEvent.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<ClaudeCodeInternalEvent>, I>>(
|
||||
object: I,
|
||||
): ClaudeCodeInternalEvent {
|
||||
const message = createBaseClaudeCodeInternalEvent()
|
||||
message.event_name = object.event_name ?? ''
|
||||
message.client_timestamp = object.client_timestamp ?? undefined
|
||||
message.model = object.model ?? ''
|
||||
message.session_id = object.session_id ?? ''
|
||||
message.user_type = object.user_type ?? ''
|
||||
message.betas = object.betas ?? ''
|
||||
message.env =
|
||||
object.env !== undefined && object.env !== null
|
||||
? EnvironmentMetadata.fromPartial(object.env)
|
||||
: undefined
|
||||
message.entrypoint = object.entrypoint ?? ''
|
||||
message.agent_sdk_version = object.agent_sdk_version ?? ''
|
||||
message.is_interactive = object.is_interactive ?? false
|
||||
message.client_type = object.client_type ?? ''
|
||||
message.process = object.process ?? ''
|
||||
message.additional_metadata = object.additional_metadata ?? ''
|
||||
message.auth =
|
||||
object.auth !== undefined && object.auth !== null
|
||||
? PublicApiAuth.fromPartial(object.auth)
|
||||
: undefined
|
||||
message.server_timestamp = object.server_timestamp ?? undefined
|
||||
message.event_id = object.event_id ?? ''
|
||||
message.device_id = object.device_id ?? ''
|
||||
message.swe_bench_run_id = object.swe_bench_run_id ?? ''
|
||||
message.swe_bench_instance_id = object.swe_bench_instance_id ?? ''
|
||||
message.swe_bench_task_id = object.swe_bench_task_id ?? ''
|
||||
message.email = object.email ?? ''
|
||||
message.agent_id = object.agent_id ?? ''
|
||||
message.parent_session_id = object.parent_session_id ?? ''
|
||||
message.agent_type = object.agent_type ?? ''
|
||||
message.slack =
|
||||
object.slack !== undefined && object.slack !== null
|
||||
? SlackContext.fromPartial(object.slack)
|
||||
: undefined
|
||||
message.team_name = object.team_name ?? ''
|
||||
message.skill_name = object.skill_name ?? ''
|
||||
message.plugin_name = object.plugin_name ?? ''
|
||||
message.marketplace_name = object.marketplace_name ?? ''
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
type Builtin =
|
||||
| Date
|
||||
| Function
|
||||
| Uint8Array
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| undefined
|
||||
|
||||
type DeepPartial<T> = T extends Builtin
|
||||
? T
|
||||
: T extends globalThis.Array<infer U>
|
||||
? globalThis.Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {}
|
||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>
|
||||
|
||||
type KeysOfUnion<T> = T extends T ? keyof T : never
|
||||
type Exact<P, I extends P> = P extends Builtin
|
||||
? P
|
||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
||||
[K in Exclude<keyof I, KeysOfUnion<P>>]: never
|
||||
}
|
||||
|
||||
function fromTimestamp(t: Timestamp): Date {
|
||||
let millis = (t.seconds || 0) * 1_000
|
||||
millis += (t.nanos || 0) / 1_000_000
|
||||
return new globalThis.Date(millis)
|
||||
}
|
||||
|
||||
function fromJsonTimestamp(o: any): Date {
|
||||
if (o instanceof globalThis.Date) {
|
||||
return o
|
||||
} else if (typeof o === 'string') {
|
||||
return new globalThis.Date(o)
|
||||
} else {
|
||||
return fromTimestamp(Timestamp.fromJSON(o))
|
||||
}
|
||||
}
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined
|
||||
}
|
||||
|
||||
interface MessageFns<T> {
|
||||
fromJSON(object: any): T
|
||||
toJSON(message: T): unknown
|
||||
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
|
||||
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.6.1
|
||||
// protoc unknown
|
||||
// source: events_mono/common/v1/auth.proto
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
/** PublicApiAuth contains authentication context automatically injected by the API */
|
||||
export interface PublicApiAuth {
|
||||
account_id?: number | undefined
|
||||
organization_uuid?: string | undefined
|
||||
account_uuid?: string | undefined
|
||||
}
|
||||
|
||||
function createBasePublicApiAuth(): PublicApiAuth {
|
||||
return { account_id: 0, organization_uuid: '', account_uuid: '' }
|
||||
}
|
||||
|
||||
export const PublicApiAuth: MessageFns<PublicApiAuth> = {
|
||||
fromJSON(object: any): PublicApiAuth {
|
||||
return {
|
||||
account_id: isSet(object.account_id)
|
||||
? globalThis.Number(object.account_id)
|
||||
: 0,
|
||||
organization_uuid: isSet(object.organization_uuid)
|
||||
? globalThis.String(object.organization_uuid)
|
||||
: '',
|
||||
account_uuid: isSet(object.account_uuid)
|
||||
? globalThis.String(object.account_uuid)
|
||||
: '',
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: PublicApiAuth): unknown {
|
||||
const obj: any = {}
|
||||
if (message.account_id !== undefined) {
|
||||
obj.account_id = Math.round(message.account_id)
|
||||
}
|
||||
if (message.organization_uuid !== undefined) {
|
||||
obj.organization_uuid = message.organization_uuid
|
||||
}
|
||||
if (message.account_uuid !== undefined) {
|
||||
obj.account_uuid = message.account_uuid
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<PublicApiAuth>, I>>(
|
||||
base?: I,
|
||||
): PublicApiAuth {
|
||||
return PublicApiAuth.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<PublicApiAuth>, I>>(
|
||||
object: I,
|
||||
): PublicApiAuth {
|
||||
const message = createBasePublicApiAuth()
|
||||
message.account_id = object.account_id ?? 0
|
||||
message.organization_uuid = object.organization_uuid ?? ''
|
||||
message.account_uuid = object.account_uuid ?? ''
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
type Builtin =
|
||||
| Date
|
||||
| Function
|
||||
| Uint8Array
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| undefined
|
||||
|
||||
type DeepPartial<T> = T extends Builtin
|
||||
? T
|
||||
: T extends globalThis.Array<infer U>
|
||||
? globalThis.Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {}
|
||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>
|
||||
|
||||
type KeysOfUnion<T> = T extends T ? keyof T : never
|
||||
type Exact<P, I extends P> = P extends Builtin
|
||||
? P
|
||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
||||
[K in Exclude<keyof I, KeysOfUnion<P>>]: never
|
||||
}
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined
|
||||
}
|
||||
|
||||
interface MessageFns<T> {
|
||||
fromJSON(object: any): T
|
||||
toJSON(message: T): unknown
|
||||
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
|
||||
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.6.1
|
||||
// protoc unknown
|
||||
// source: events_mono/growthbook/v1/growthbook_experiment_event.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { Timestamp } from '../../../google/protobuf/timestamp.js'
|
||||
import { PublicApiAuth } from '../../common/v1/auth.js'
|
||||
|
||||
/**
|
||||
* GrowthBook experiment assignment event
|
||||
* This event tracks when a user is exposed to an experiment variant
|
||||
* See: https://docs.growthbook.io/guide/bigquery
|
||||
*/
|
||||
export interface GrowthbookExperimentEvent {
|
||||
/** Unique event identifier (for deduplication) */
|
||||
event_id?: string | undefined
|
||||
/** When user was exposed to experiment (maps to GrowthBook's timestamp column) */
|
||||
timestamp?: Date | undefined
|
||||
/** Experiment tracking key (maps to GrowthBook's experiment_id column) */
|
||||
experiment_id?: string | undefined
|
||||
/** Variation index: 0=control, 1+=variants (maps to GrowthBook's variation_id column) */
|
||||
variation_id?: number | undefined
|
||||
/** Environment where assignment occurred */
|
||||
environment?: string | undefined
|
||||
/** User attributes at time of assignment */
|
||||
user_attributes?: string | undefined
|
||||
/** Experiment metadata */
|
||||
experiment_metadata?: string | undefined
|
||||
/** Device identifier for the client */
|
||||
device_id?: string | undefined
|
||||
/** Authentication context automatically injected by the API */
|
||||
auth?: PublicApiAuth | undefined
|
||||
/** Session identifier for tracking user sessions */
|
||||
session_id?: string | undefined
|
||||
/** Anonymous identifier for unauthenticated users */
|
||||
anonymous_id?: string | undefined
|
||||
/** Event metadata variables (automatically populated by internal-tools-common event_logging library) */
|
||||
event_metadata_vars?: string | undefined
|
||||
}
|
||||
|
||||
function createBaseGrowthbookExperimentEvent(): GrowthbookExperimentEvent {
|
||||
return {
|
||||
event_id: '',
|
||||
timestamp: undefined,
|
||||
experiment_id: '',
|
||||
variation_id: 0,
|
||||
environment: '',
|
||||
user_attributes: '',
|
||||
experiment_metadata: '',
|
||||
device_id: '',
|
||||
auth: undefined,
|
||||
session_id: '',
|
||||
anonymous_id: '',
|
||||
event_metadata_vars: '',
|
||||
}
|
||||
}
|
||||
|
||||
export const GrowthbookExperimentEvent: MessageFns<GrowthbookExperimentEvent> =
|
||||
{
|
||||
fromJSON(object: any): GrowthbookExperimentEvent {
|
||||
return {
|
||||
event_id: isSet(object.event_id)
|
||||
? globalThis.String(object.event_id)
|
||||
: '',
|
||||
timestamp: isSet(object.timestamp)
|
||||
? fromJsonTimestamp(object.timestamp)
|
||||
: undefined,
|
||||
experiment_id: isSet(object.experiment_id)
|
||||
? globalThis.String(object.experiment_id)
|
||||
: '',
|
||||
variation_id: isSet(object.variation_id)
|
||||
? globalThis.Number(object.variation_id)
|
||||
: 0,
|
||||
environment: isSet(object.environment)
|
||||
? globalThis.String(object.environment)
|
||||
: '',
|
||||
user_attributes: isSet(object.user_attributes)
|
||||
? globalThis.String(object.user_attributes)
|
||||
: '',
|
||||
experiment_metadata: isSet(object.experiment_metadata)
|
||||
? globalThis.String(object.experiment_metadata)
|
||||
: '',
|
||||
device_id: isSet(object.device_id)
|
||||
? globalThis.String(object.device_id)
|
||||
: '',
|
||||
auth: isSet(object.auth)
|
||||
? PublicApiAuth.fromJSON(object.auth)
|
||||
: undefined,
|
||||
session_id: isSet(object.session_id)
|
||||
? globalThis.String(object.session_id)
|
||||
: '',
|
||||
anonymous_id: isSet(object.anonymous_id)
|
||||
? globalThis.String(object.anonymous_id)
|
||||
: '',
|
||||
event_metadata_vars: isSet(object.event_metadata_vars)
|
||||
? globalThis.String(object.event_metadata_vars)
|
||||
: '',
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: GrowthbookExperimentEvent): unknown {
|
||||
const obj: any = {}
|
||||
if (message.event_id !== undefined) {
|
||||
obj.event_id = message.event_id
|
||||
}
|
||||
if (message.timestamp !== undefined) {
|
||||
obj.timestamp = message.timestamp.toISOString()
|
||||
}
|
||||
if (message.experiment_id !== undefined) {
|
||||
obj.experiment_id = message.experiment_id
|
||||
}
|
||||
if (message.variation_id !== undefined) {
|
||||
obj.variation_id = Math.round(message.variation_id)
|
||||
}
|
||||
if (message.environment !== undefined) {
|
||||
obj.environment = message.environment
|
||||
}
|
||||
if (message.user_attributes !== undefined) {
|
||||
obj.user_attributes = message.user_attributes
|
||||
}
|
||||
if (message.experiment_metadata !== undefined) {
|
||||
obj.experiment_metadata = message.experiment_metadata
|
||||
}
|
||||
if (message.device_id !== undefined) {
|
||||
obj.device_id = message.device_id
|
||||
}
|
||||
if (message.auth !== undefined) {
|
||||
obj.auth = PublicApiAuth.toJSON(message.auth)
|
||||
}
|
||||
if (message.session_id !== undefined) {
|
||||
obj.session_id = message.session_id
|
||||
}
|
||||
if (message.anonymous_id !== undefined) {
|
||||
obj.anonymous_id = message.anonymous_id
|
||||
}
|
||||
if (message.event_metadata_vars !== undefined) {
|
||||
obj.event_metadata_vars = message.event_metadata_vars
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<GrowthbookExperimentEvent>, I>>(
|
||||
base?: I,
|
||||
): GrowthbookExperimentEvent {
|
||||
return GrowthbookExperimentEvent.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<GrowthbookExperimentEvent>, I>>(
|
||||
object: I,
|
||||
): GrowthbookExperimentEvent {
|
||||
const message = createBaseGrowthbookExperimentEvent()
|
||||
message.event_id = object.event_id ?? ''
|
||||
message.timestamp = object.timestamp ?? undefined
|
||||
message.experiment_id = object.experiment_id ?? ''
|
||||
message.variation_id = object.variation_id ?? 0
|
||||
message.environment = object.environment ?? ''
|
||||
message.user_attributes = object.user_attributes ?? ''
|
||||
message.experiment_metadata = object.experiment_metadata ?? ''
|
||||
message.device_id = object.device_id ?? ''
|
||||
message.auth =
|
||||
object.auth !== undefined && object.auth !== null
|
||||
? PublicApiAuth.fromPartial(object.auth)
|
||||
: undefined
|
||||
message.session_id = object.session_id ?? ''
|
||||
message.anonymous_id = object.anonymous_id ?? ''
|
||||
message.event_metadata_vars = object.event_metadata_vars ?? ''
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
type Builtin =
|
||||
| Date
|
||||
| Function
|
||||
| Uint8Array
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| undefined
|
||||
|
||||
type DeepPartial<T> = T extends Builtin
|
||||
? T
|
||||
: T extends globalThis.Array<infer U>
|
||||
? globalThis.Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {}
|
||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>
|
||||
|
||||
type KeysOfUnion<T> = T extends T ? keyof T : never
|
||||
type Exact<P, I extends P> = P extends Builtin
|
||||
? P
|
||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
||||
[K in Exclude<keyof I, KeysOfUnion<P>>]: never
|
||||
}
|
||||
|
||||
function fromTimestamp(t: Timestamp): Date {
|
||||
let millis = (t.seconds || 0) * 1_000
|
||||
millis += (t.nanos || 0) / 1_000_000
|
||||
return new globalThis.Date(millis)
|
||||
}
|
||||
|
||||
function fromJsonTimestamp(o: any): Date {
|
||||
if (o instanceof globalThis.Date) {
|
||||
return o
|
||||
} else if (typeof o === 'string') {
|
||||
return new globalThis.Date(o)
|
||||
} else {
|
||||
return fromTimestamp(Timestamp.fromJSON(o))
|
||||
}
|
||||
}
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined
|
||||
}
|
||||
|
||||
interface MessageFns<T> {
|
||||
fromJSON(object: any): T
|
||||
toJSON(message: T): unknown
|
||||
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
|
||||
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.6.1
|
||||
// protoc unknown
|
||||
// source: google/protobuf/timestamp.proto
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* A Timestamp represents a point in time independent of any time zone or local
|
||||
* calendar, encoded as a count of seconds and fractions of seconds at
|
||||
* nanosecond resolution. The count is relative to an epoch at UTC midnight on
|
||||
* January 1, 1970, in the proleptic Gregorian calendar which extends the
|
||||
* Gregorian calendar backwards to year one.
|
||||
*
|
||||
* All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
|
||||
* second table is needed for interpretation, using a [24-hour linear
|
||||
* smear](https://developers.google.com/time/smear).
|
||||
*
|
||||
* The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
|
||||
* restricting to that range, we ensure that we can convert to and from [RFC
|
||||
* 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* Example 1: Compute Timestamp from POSIX `time()`.
|
||||
*
|
||||
* Timestamp timestamp;
|
||||
* timestamp.set_seconds(time(NULL));
|
||||
* timestamp.set_nanos(0);
|
||||
*
|
||||
* Example 2: Compute Timestamp from POSIX `gettimeofday()`.
|
||||
*
|
||||
* struct timeval tv;
|
||||
* gettimeofday(&tv, NULL);
|
||||
*
|
||||
* Timestamp timestamp;
|
||||
* timestamp.set_seconds(tv.tv_sec);
|
||||
* timestamp.set_nanos(tv.tv_usec * 1000);
|
||||
*
|
||||
* Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
|
||||
*
|
||||
* FILETIME ft;
|
||||
* GetSystemTimeAsFileTime(&ft);
|
||||
* UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||
*
|
||||
* // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
|
||||
* // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
|
||||
* Timestamp timestamp;
|
||||
* timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
|
||||
* timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
|
||||
*
|
||||
* Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
|
||||
*
|
||||
* long millis = System.currentTimeMillis();
|
||||
*
|
||||
* Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
|
||||
* .setNanos((int) ((millis % 1000) * 1000000)).build();
|
||||
*
|
||||
* Example 5: Compute Timestamp from Java `Instant.now()`.
|
||||
*
|
||||
* Instant now = Instant.now();
|
||||
*
|
||||
* Timestamp timestamp =
|
||||
* Timestamp.newBuilder().setSeconds(now.getEpochSecond())
|
||||
* .setNanos(now.getNano()).build();
|
||||
*
|
||||
* Example 6: Compute Timestamp from current time in Python.
|
||||
*
|
||||
* timestamp = Timestamp()
|
||||
* timestamp.GetCurrentTime()
|
||||
*
|
||||
* # JSON Mapping
|
||||
*
|
||||
* In JSON format, the Timestamp type is encoded as a string in the
|
||||
* [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
|
||||
* format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
|
||||
* where {year} is always expressed using four digits while {month}, {day},
|
||||
* {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
|
||||
* seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
|
||||
* are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
|
||||
* is required. A proto3 JSON serializer should always use UTC (as indicated by
|
||||
* "Z") when printing the Timestamp type and a proto3 JSON parser should be
|
||||
* able to accept both UTC and other timezones (as indicated by an offset).
|
||||
*
|
||||
* For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
|
||||
* 01:30 UTC on January 15, 2017.
|
||||
*
|
||||
* In JavaScript, one can convert a Date object to this format using the
|
||||
* standard
|
||||
* [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
|
||||
* method. In Python, a standard `datetime.datetime` object can be converted
|
||||
* to this format using
|
||||
* [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
|
||||
* the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
|
||||
* the Joda Time's [`ISODateTimeFormat.dateTime()`](
|
||||
* http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()
|
||||
* ) to obtain a formatter capable of generating timestamps in this format.
|
||||
*/
|
||||
export interface Timestamp {
|
||||
/**
|
||||
* Represents seconds of UTC time since Unix epoch
|
||||
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||
* 9999-12-31T23:59:59Z inclusive.
|
||||
*/
|
||||
seconds?: number | undefined
|
||||
/**
|
||||
* Non-negative fractions of a second at nanosecond resolution. Negative
|
||||
* second values with fractions must still have non-negative nanos values
|
||||
* that count forward in time. Must be from 0 to 999,999,999
|
||||
* inclusive.
|
||||
*/
|
||||
nanos?: number | undefined
|
||||
}
|
||||
|
||||
function createBaseTimestamp(): Timestamp {
|
||||
return { seconds: 0, nanos: 0 }
|
||||
}
|
||||
|
||||
export const Timestamp: MessageFns<Timestamp> = {
|
||||
fromJSON(object: any): Timestamp {
|
||||
return {
|
||||
seconds: isSet(object.seconds) ? globalThis.Number(object.seconds) : 0,
|
||||
nanos: isSet(object.nanos) ? globalThis.Number(object.nanos) : 0,
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: Timestamp): unknown {
|
||||
const obj: any = {}
|
||||
if (message.seconds !== undefined) {
|
||||
obj.seconds = Math.round(message.seconds)
|
||||
}
|
||||
if (message.nanos !== undefined) {
|
||||
obj.nanos = Math.round(message.nanos)
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<Timestamp>, I>>(base?: I): Timestamp {
|
||||
return Timestamp.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<Timestamp>, I>>(
|
||||
object: I,
|
||||
): Timestamp {
|
||||
const message = createBaseTimestamp()
|
||||
message.seconds = object.seconds ?? 0
|
||||
message.nanos = object.nanos ?? 0
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
type Builtin =
|
||||
| Date
|
||||
| Function
|
||||
| Uint8Array
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| undefined
|
||||
|
||||
type DeepPartial<T> = T extends Builtin
|
||||
? T
|
||||
: T extends globalThis.Array<infer U>
|
||||
? globalThis.Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {}
|
||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>
|
||||
|
||||
type KeysOfUnion<T> = T extends T ? keyof T : never
|
||||
type Exact<P, I extends P> = P extends Builtin
|
||||
? P
|
||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
||||
[K in Exclude<keyof I, KeysOfUnion<P>>]: never
|
||||
}
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined
|
||||
}
|
||||
|
||||
interface MessageFns<T> {
|
||||
fromJSON(object: any): T
|
||||
toJSON(message: T): unknown
|
||||
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T
|
||||
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
||||
import { z } from 'zod/v4'
|
||||
import { lazySchema } from '../utils/lazySchema.js'
|
||||
import {
|
||||
type HookEvent,
|
||||
HOOK_EVENTS,
|
||||
type HookInput,
|
||||
type PermissionUpdate,
|
||||
} from 'src/entrypoints/agentSdkTypes.js'
|
||||
import type {
|
||||
HookJSONOutput,
|
||||
AsyncHookJSONOutput,
|
||||
SyncHookJSONOutput,
|
||||
} from 'src/entrypoints/agentSdkTypes.js'
|
||||
import type { Message } from 'src/types/message.js'
|
||||
import type { PermissionResult } from 'src/utils/permissions/PermissionResult.js'
|
||||
import { permissionBehaviorSchema } from 'src/utils/permissions/PermissionRule.js'
|
||||
import { permissionUpdateSchema } from 'src/utils/permissions/PermissionUpdateSchema.js'
|
||||
import type { AppState } from '../state/AppState.js'
|
||||
import type { AttributionState } from '../utils/commitAttribution.js'
|
||||
|
||||
export function isHookEvent(value: string): value is HookEvent {
|
||||
return HOOK_EVENTS.includes(value as HookEvent)
|
||||
}
|
||||
|
||||
// Prompt elicitation protocol types. The `prompt` key acts as discriminator
|
||||
// (mirroring the {async:true} pattern), with the id as its value.
|
||||
export const promptRequestSchema = lazySchema(() =>
|
||||
z.object({
|
||||
prompt: z.string(), // request id
|
||||
message: z.string(),
|
||||
options: z.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
label: z.string(),
|
||||
description: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export type PromptRequest = z.infer<ReturnType<typeof promptRequestSchema>>
|
||||
|
||||
export type PromptResponse = {
|
||||
prompt_response: string // request id
|
||||
selected: string
|
||||
}
|
||||
|
||||
// Sync hook response schema
|
||||
export const syncHookResponseSchema = lazySchema(() =>
|
||||
z.object({
|
||||
continue: z
|
||||
.boolean()
|
||||
.describe('Whether Claude should continue after hook (default: true)')
|
||||
.optional(),
|
||||
suppressOutput: z
|
||||
.boolean()
|
||||
.describe('Hide stdout from transcript (default: false)')
|
||||
.optional(),
|
||||
stopReason: z
|
||||
.string()
|
||||
.describe('Message shown when continue is false')
|
||||
.optional(),
|
||||
decision: z.enum(['approve', 'block']).optional(),
|
||||
reason: z.string().describe('Explanation for the decision').optional(),
|
||||
systemMessage: z
|
||||
.string()
|
||||
.describe('Warning message shown to the user')
|
||||
.optional(),
|
||||
hookSpecificOutput: z
|
||||
.union([
|
||||
z.object({
|
||||
hookEventName: z.literal('PreToolUse'),
|
||||
permissionDecision: permissionBehaviorSchema().optional(),
|
||||
permissionDecisionReason: z.string().optional(),
|
||||
updatedInput: z.record(z.string(), z.unknown()).optional(),
|
||||
additionalContext: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('UserPromptSubmit'),
|
||||
additionalContext: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('SessionStart'),
|
||||
additionalContext: z.string().optional(),
|
||||
initialUserMessage: z.string().optional(),
|
||||
watchPaths: z
|
||||
.array(z.string())
|
||||
.describe('Absolute paths to watch for FileChanged hooks')
|
||||
.optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('Setup'),
|
||||
additionalContext: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('SubagentStart'),
|
||||
additionalContext: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('PostToolUse'),
|
||||
additionalContext: z.string().optional(),
|
||||
updatedMCPToolOutput: z
|
||||
.unknown()
|
||||
.describe('Updates the output for MCP tools')
|
||||
.optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('PostToolUseFailure'),
|
||||
additionalContext: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('PermissionDenied'),
|
||||
retry: z.boolean().optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('Notification'),
|
||||
additionalContext: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('PermissionRequest'),
|
||||
decision: z.union([
|
||||
z.object({
|
||||
behavior: z.literal('allow'),
|
||||
updatedInput: z.record(z.string(), z.unknown()).optional(),
|
||||
updatedPermissions: z.array(permissionUpdateSchema()).optional(),
|
||||
}),
|
||||
z.object({
|
||||
behavior: z.literal('deny'),
|
||||
message: z.string().optional(),
|
||||
interrupt: z.boolean().optional(),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('Elicitation'),
|
||||
action: z.enum(['accept', 'decline', 'cancel']).optional(),
|
||||
content: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('ElicitationResult'),
|
||||
action: z.enum(['accept', 'decline', 'cancel']).optional(),
|
||||
content: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('CwdChanged'),
|
||||
watchPaths: z
|
||||
.array(z.string())
|
||||
.describe('Absolute paths to watch for FileChanged hooks')
|
||||
.optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('FileChanged'),
|
||||
watchPaths: z
|
||||
.array(z.string())
|
||||
.describe('Absolute paths to watch for FileChanged hooks')
|
||||
.optional(),
|
||||
}),
|
||||
z.object({
|
||||
hookEventName: z.literal('WorktreeCreate'),
|
||||
worktreePath: z.string(),
|
||||
}),
|
||||
])
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
|
||||
// Zod schema for hook JSON output validation
|
||||
export const hookJSONOutputSchema = lazySchema(() => {
|
||||
// Async hook response schema
|
||||
const asyncHookResponseSchema = z.object({
|
||||
async: z.literal(true),
|
||||
asyncTimeout: z.number().optional(),
|
||||
})
|
||||
return z.union([asyncHookResponseSchema, syncHookResponseSchema()])
|
||||
})
|
||||
|
||||
// Infer the TypeScript type from the schema
|
||||
type SchemaHookJSONOutput = z.infer<ReturnType<typeof hookJSONOutputSchema>>
|
||||
|
||||
// Type guard function to check if response is sync
|
||||
export function isSyncHookJSONOutput(
|
||||
json: HookJSONOutput,
|
||||
): json is SyncHookJSONOutput {
|
||||
return !('async' in json && json.async === true)
|
||||
}
|
||||
|
||||
// Type guard function to check if response is async
|
||||
export function isAsyncHookJSONOutput(
|
||||
json: HookJSONOutput,
|
||||
): json is AsyncHookJSONOutput {
|
||||
return 'async' in json && json.async === true
|
||||
}
|
||||
|
||||
// Compile-time assertion that SDK and Zod types match
|
||||
import type { IsEqual } from 'type-fest'
|
||||
type Assert<T extends true> = T
|
||||
type _assertSDKTypesMatch = Assert<
|
||||
IsEqual<SchemaHookJSONOutput, HookJSONOutput>
|
||||
>
|
||||
|
||||
/** Context passed to callback hooks for state access */
|
||||
export type HookCallbackContext = {
|
||||
getAppState: () => AppState
|
||||
updateAttributionState: (
|
||||
updater: (prev: AttributionState) => AttributionState,
|
||||
) => void
|
||||
}
|
||||
|
||||
/** Hook that is a callback. */
|
||||
export type HookCallback = {
|
||||
type: 'callback'
|
||||
callback: (
|
||||
input: HookInput,
|
||||
toolUseID: string | null,
|
||||
abort: AbortSignal | undefined,
|
||||
/** Hook index for SessionStart hooks to compute CLAUDE_ENV_FILE path */
|
||||
hookIndex?: number,
|
||||
/** Optional context for accessing app state */
|
||||
context?: HookCallbackContext,
|
||||
) => Promise<HookJSONOutput>
|
||||
/** Timeout in seconds for this hook */
|
||||
timeout?: number
|
||||
/** Internal hooks (e.g. session file access analytics) are excluded from tengu_run_hook metrics */
|
||||
internal?: boolean
|
||||
}
|
||||
|
||||
export type HookCallbackMatcher = {
|
||||
matcher?: string
|
||||
hooks: HookCallback[]
|
||||
pluginName?: string
|
||||
}
|
||||
|
||||
export type HookProgress = {
|
||||
type: 'hook_progress'
|
||||
hookEvent: HookEvent
|
||||
hookName: string
|
||||
command: string
|
||||
promptText?: string
|
||||
statusMessage?: string
|
||||
}
|
||||
|
||||
export type HookBlockingError = {
|
||||
blockingError: string
|
||||
command: string
|
||||
}
|
||||
|
||||
export type PermissionRequestResult =
|
||||
| {
|
||||
behavior: 'allow'
|
||||
updatedInput?: Record<string, unknown>
|
||||
updatedPermissions?: PermissionUpdate[]
|
||||
}
|
||||
| {
|
||||
behavior: 'deny'
|
||||
message?: string
|
||||
interrupt?: boolean
|
||||
}
|
||||
|
||||
export type HookResult = {
|
||||
message?: Message
|
||||
systemMessage?: Message
|
||||
blockingError?: HookBlockingError
|
||||
outcome: 'success' | 'blocking' | 'non_blocking_error' | 'cancelled'
|
||||
preventContinuation?: boolean
|
||||
stopReason?: string
|
||||
permissionBehavior?: 'ask' | 'deny' | 'allow' | 'passthrough'
|
||||
hookPermissionDecisionReason?: string
|
||||
additionalContext?: string
|
||||
initialUserMessage?: string
|
||||
updatedInput?: Record<string, unknown>
|
||||
updatedMCPToolOutput?: unknown
|
||||
permissionRequestResult?: PermissionRequestResult
|
||||
retry?: boolean
|
||||
}
|
||||
|
||||
export type AggregatedHookResult = {
|
||||
message?: Message
|
||||
blockingErrors?: HookBlockingError[]
|
||||
preventContinuation?: boolean
|
||||
stopReason?: string
|
||||
hookPermissionDecisionReason?: string
|
||||
permissionBehavior?: PermissionResult['behavior']
|
||||
additionalContexts?: string[]
|
||||
initialUserMessage?: string
|
||||
updatedInput?: Record<string, unknown>
|
||||
updatedMCPToolOutput?: unknown
|
||||
permissionRequestResult?: PermissionRequestResult
|
||||
retry?: boolean
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Branded types for session and agent IDs.
|
||||
* These prevent accidentally mixing up session IDs and agent IDs at compile time.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A session ID uniquely identifies a Claude Code session.
|
||||
* Returned by getSessionId().
|
||||
*/
|
||||
export type SessionId = string & { readonly __brand: 'SessionId' }
|
||||
|
||||
/**
|
||||
* An agent ID uniquely identifies a subagent within a session.
|
||||
* Returned by createAgentId().
|
||||
* When present, indicates the context is a subagent (not the main session).
|
||||
*/
|
||||
export type AgentId = string & { readonly __brand: 'AgentId' }
|
||||
|
||||
/**
|
||||
* Cast a raw string to SessionId.
|
||||
* Use sparingly - prefer getSessionId() when possible.
|
||||
*/
|
||||
export function asSessionId(id: string): SessionId {
|
||||
return id as SessionId
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast a raw string to AgentId.
|
||||
* Use sparingly - prefer createAgentId() when possible.
|
||||
*/
|
||||
export function asAgentId(id: string): AgentId {
|
||||
return id as AgentId
|
||||
}
|
||||
|
||||
const AGENT_ID_PATTERN = /^a(?:.+-)?[0-9a-f]{16}$/
|
||||
|
||||
/**
|
||||
* Validate and brand a string as AgentId.
|
||||
* Matches the format produced by createAgentId(): `a` + optional `<label>-` + 16 hex chars.
|
||||
* Returns null if the string doesn't match (e.g. teammate names, team-addressing).
|
||||
*/
|
||||
export function toAgentId(s: string): AgentId | null {
|
||||
return AGENT_ID_PATTERN.test(s) ? (s as AgentId) : null
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
import type { UUID } from 'crypto'
|
||||
import type { FileHistorySnapshot } from 'src/utils/fileHistory.js'
|
||||
import type { ContentReplacementRecord } from 'src/utils/toolResultStorage.js'
|
||||
import type { AgentId } from './ids.js'
|
||||
import type { Message } from './message.js'
|
||||
import type { QueueOperationMessage } from './messageQueueTypes.js'
|
||||
|
||||
export type SerializedMessage = Message & {
|
||||
cwd: string
|
||||
userType: string
|
||||
entrypoint?: string // CLAUDE_CODE_ENTRYPOINT — distinguishes cli/sdk-ts/sdk-py/etc.
|
||||
sessionId: string
|
||||
timestamp: string
|
||||
version: string
|
||||
gitBranch?: string
|
||||
slug?: string // Session slug for files like plans (used for resume)
|
||||
}
|
||||
|
||||
export type LogOption = {
|
||||
date: string
|
||||
messages: SerializedMessage[]
|
||||
fullPath?: string
|
||||
value: number
|
||||
created: Date
|
||||
modified: Date
|
||||
firstPrompt: string
|
||||
messageCount: number
|
||||
fileSize?: number // File size in bytes (for display)
|
||||
isSidechain: boolean
|
||||
isLite?: boolean // True for lite logs (messages not loaded)
|
||||
sessionId?: string // Session ID for lite logs
|
||||
teamName?: string // Team name if this is a spawned agent session
|
||||
agentName?: string // Agent's custom name (from /rename or swarm)
|
||||
agentColor?: string // Agent's color (from /rename or swarm)
|
||||
agentSetting?: string // Agent definition used (from --agent flag or settings.agent)
|
||||
isTeammate?: boolean // Whether this session was created by a swarm teammate
|
||||
leafUuid?: UUID // If given, this uuid must appear in the DB
|
||||
summary?: string // Optional conversation summary
|
||||
customTitle?: string // Optional user-set custom title
|
||||
tag?: string // Optional tag for the session (searchable in /resume)
|
||||
fileHistorySnapshots?: FileHistorySnapshot[] // Optional file history snapshots
|
||||
attributionSnapshots?: AttributionSnapshotMessage[] // Optional attribution snapshots
|
||||
contextCollapseCommits?: ContextCollapseCommitEntry[] // Ordered — commit B may reference commit A's summary
|
||||
contextCollapseSnapshot?: ContextCollapseSnapshotEntry // Last-wins — staged queue + spawn state
|
||||
gitBranch?: string // Git branch at the end of the session
|
||||
projectPath?: string // Original project directory path
|
||||
prNumber?: number // GitHub PR number linked to this session
|
||||
prUrl?: string // Full URL to the linked PR
|
||||
prRepository?: string // Repository in "owner/repo" format
|
||||
mode?: 'coordinator' | 'normal' // Session mode for coordinator/normal detection
|
||||
worktreeSession?: PersistedWorktreeSession | null // Worktree state at session end (null = exited, undefined = never entered)
|
||||
contentReplacements?: ContentReplacementRecord[] // Replacement decisions for resume reconstruction
|
||||
}
|
||||
|
||||
export type SummaryMessage = {
|
||||
type: 'summary'
|
||||
leafUuid: UUID
|
||||
summary: string
|
||||
}
|
||||
|
||||
export type CustomTitleMessage = {
|
||||
type: 'custom-title'
|
||||
sessionId: UUID
|
||||
customTitle: string
|
||||
}
|
||||
|
||||
/**
|
||||
* AI-generated session title. Distinct from CustomTitleMessage so that:
|
||||
* - User renames (custom-title) always win over AI titles in read preference
|
||||
* - reAppendSessionMetadata never re-appends AI titles (they're ephemeral/
|
||||
* regeneratable; re-appending would clobber user renames on resume)
|
||||
* - VS Code's onlyIfNoCustomTitle CAS check only matches user titles,
|
||||
* allowing AI to overwrite its own previous AI title but not user titles
|
||||
*/
|
||||
export type AiTitleMessage = {
|
||||
type: 'ai-title'
|
||||
sessionId: UUID
|
||||
aiTitle: string
|
||||
}
|
||||
|
||||
export type LastPromptMessage = {
|
||||
type: 'last-prompt'
|
||||
sessionId: UUID
|
||||
lastPrompt: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodic fork-generated summary of what the agent is currently doing.
|
||||
* Written every min(5 steps, 2min) by forking the main thread mid-turn so
|
||||
* `claude ps` can show something more useful than the last user prompt
|
||||
* (which is often "ok go" or "fix it").
|
||||
*/
|
||||
export type TaskSummaryMessage = {
|
||||
type: 'task-summary'
|
||||
sessionId: UUID
|
||||
summary: string
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export type TagMessage = {
|
||||
type: 'tag'
|
||||
sessionId: UUID
|
||||
tag: string
|
||||
}
|
||||
|
||||
export type AgentNameMessage = {
|
||||
type: 'agent-name'
|
||||
sessionId: UUID
|
||||
agentName: string
|
||||
}
|
||||
|
||||
export type AgentColorMessage = {
|
||||
type: 'agent-color'
|
||||
sessionId: UUID
|
||||
agentColor: string
|
||||
}
|
||||
|
||||
export type AgentSettingMessage = {
|
||||
type: 'agent-setting'
|
||||
sessionId: UUID
|
||||
agentSetting: string
|
||||
}
|
||||
|
||||
/**
|
||||
* PR link message stored in session transcript.
|
||||
* Links a session to a GitHub pull request for tracking and navigation.
|
||||
*/
|
||||
export type PRLinkMessage = {
|
||||
type: 'pr-link'
|
||||
sessionId: UUID
|
||||
prNumber: number
|
||||
prUrl: string
|
||||
prRepository: string // e.g., "owner/repo"
|
||||
timestamp: string // ISO timestamp when linked
|
||||
}
|
||||
|
||||
export type ModeEntry = {
|
||||
type: 'mode'
|
||||
sessionId: UUID
|
||||
mode: 'coordinator' | 'normal'
|
||||
}
|
||||
|
||||
/**
|
||||
* Worktree session state persisted to the transcript for resume.
|
||||
* Subset of WorktreeSession from utils/worktree.ts — excludes ephemeral
|
||||
* fields (creationDurationMs, usedSparsePaths) that are only used for
|
||||
* first-run analytics.
|
||||
*/
|
||||
export type PersistedWorktreeSession = {
|
||||
originalCwd: string
|
||||
worktreePath: string
|
||||
worktreeName: string
|
||||
worktreeBranch?: string
|
||||
originalBranch?: string
|
||||
originalHeadCommit?: string
|
||||
sessionId: string
|
||||
tmuxSessionName?: string
|
||||
hookBased?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Records whether the session is currently inside a worktree created by
|
||||
* EnterWorktree or --worktree. Last-wins: an enter writes the session,
|
||||
* an exit writes null. On --resume, restored only if the worktreePath
|
||||
* still exists on disk (the /exit dialog may have removed it).
|
||||
*/
|
||||
export type WorktreeStateEntry = {
|
||||
type: 'worktree-state'
|
||||
sessionId: UUID
|
||||
worktreeSession: PersistedWorktreeSession | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Records content blocks whose in-context representation was replaced with a
|
||||
* smaller stub (the full content was persisted elsewhere). Replayed on resume
|
||||
* for prompt cache stability. Written once per enforcement pass that replaces
|
||||
* at least one block. When agentId is set, the record belongs to a subagent
|
||||
* sidechain (AgentTool resume reads these); when absent, it's main-thread
|
||||
* (/resume reads these).
|
||||
*/
|
||||
export type ContentReplacementEntry = {
|
||||
type: 'content-replacement'
|
||||
sessionId: UUID
|
||||
agentId?: AgentId
|
||||
replacements: ContentReplacementRecord[]
|
||||
}
|
||||
|
||||
export type FileHistorySnapshotMessage = {
|
||||
type: 'file-history-snapshot'
|
||||
messageId: UUID
|
||||
snapshot: FileHistorySnapshot
|
||||
isSnapshotUpdate: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-file attribution state tracking Claude's character contributions.
|
||||
*/
|
||||
export type FileAttributionState = {
|
||||
contentHash: string // SHA-256 hash of file content
|
||||
claudeContribution: number // Characters written by Claude
|
||||
mtime: number // File modification time
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribution snapshot message stored in session transcript.
|
||||
* Tracks character-level contributions by Claude for commit attribution.
|
||||
*/
|
||||
export type AttributionSnapshotMessage = {
|
||||
type: 'attribution-snapshot'
|
||||
messageId: UUID
|
||||
surface: string // Client surface (cli, ide, web, api)
|
||||
fileStates: Record<string, FileAttributionState>
|
||||
promptCount?: number // Total prompts in session
|
||||
promptCountAtLastCommit?: number // Prompts at last commit
|
||||
permissionPromptCount?: number // Total permission prompts shown
|
||||
permissionPromptCountAtLastCommit?: number // Permission prompts at last commit
|
||||
escapeCount?: number // Total ESC presses (cancelled permission prompts)
|
||||
escapeCountAtLastCommit?: number // ESC presses at last commit
|
||||
}
|
||||
|
||||
export type TranscriptMessage = SerializedMessage & {
|
||||
parentUuid: UUID | null
|
||||
logicalParentUuid?: UUID | null // Preserves logical parent when parentUuid is nullified for session breaks
|
||||
isSidechain: boolean
|
||||
gitBranch?: string
|
||||
agentId?: string // Agent ID for sidechain transcripts to enable resuming agents
|
||||
teamName?: string // Team name if this is a spawned agent session
|
||||
agentName?: string // Agent's custom name (from /rename or swarm)
|
||||
agentColor?: string // Agent's color (from /rename or swarm)
|
||||
promptId?: string // Correlates with OTel prompt.id for user prompt messages
|
||||
}
|
||||
|
||||
export type SpeculationAcceptMessage = {
|
||||
type: 'speculation-accept'
|
||||
timestamp: string
|
||||
timeSavedMs: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Persisted context-collapse commit. The archived messages themselves are
|
||||
* NOT persisted — they're already in the transcript as ordinary user/
|
||||
* assistant messages. We only persist enough to reconstruct the splice
|
||||
* instruction (boundary uuids) and the summary placeholder (which is NOT
|
||||
* in the transcript because it's never yielded to the REPL).
|
||||
*
|
||||
* On restore, the store reconstructs CommittedCollapse with archived=[];
|
||||
* projectView lazily fills the archive the first time it finds the span.
|
||||
*
|
||||
* Discriminator is obfuscated to match the gate name. sessionStorage.ts
|
||||
* isn't feature-gated (it's the generic transcript plumbing used by every
|
||||
* entry type), so a descriptive string here would leak into external builds
|
||||
* via the appendEntry dispatch / loadTranscriptFile parser even though
|
||||
* nothing in an external build ever writes or reads this entry.
|
||||
*/
|
||||
export type ContextCollapseCommitEntry = {
|
||||
type: 'marble-origami-commit'
|
||||
sessionId: UUID
|
||||
/** 16-digit collapse ID. Max across entries reseeds the ID counter. */
|
||||
collapseId: string
|
||||
/** The summary placeholder's uuid — registerSummary() needs it. */
|
||||
summaryUuid: string
|
||||
/** Full <collapsed id="...">text</collapsed> string for the placeholder. */
|
||||
summaryContent: string
|
||||
/** Plain summary text for ctx_inspect. */
|
||||
summary: string
|
||||
/** Span boundaries — projectView finds these in the resumed Message[]. */
|
||||
firstArchivedUuid: string
|
||||
lastArchivedUuid: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of the staged queue and spawn trigger state. Unlike commits
|
||||
* (append-only, replay-all), snapshots are last-wins — only the most
|
||||
* recent snapshot entry is applied on restore. Written after every
|
||||
* ctx-agent spawn resolves (when staged contents may have changed).
|
||||
*
|
||||
* Staged boundaries are UUIDs (session-stable), not collapse IDs (which
|
||||
* reset with the uuidToId bimap). Restoring a staged span issues fresh
|
||||
* collapse IDs for those messages on the next decorate/display, but the
|
||||
* span itself resolves correctly.
|
||||
*/
|
||||
export type ContextCollapseSnapshotEntry = {
|
||||
type: 'marble-origami-snapshot'
|
||||
sessionId: UUID
|
||||
staged: Array<{
|
||||
startUuid: string
|
||||
endUuid: string
|
||||
summary: string
|
||||
risk: number
|
||||
stagedAt: number
|
||||
}>
|
||||
/** Spawn trigger state — so the +interval clock picks up where it left off. */
|
||||
armed: boolean
|
||||
lastSpawnTokens: number
|
||||
}
|
||||
|
||||
export type Entry =
|
||||
| TranscriptMessage
|
||||
| SummaryMessage
|
||||
| CustomTitleMessage
|
||||
| AiTitleMessage
|
||||
| LastPromptMessage
|
||||
| TaskSummaryMessage
|
||||
| TagMessage
|
||||
| AgentNameMessage
|
||||
| AgentColorMessage
|
||||
| AgentSettingMessage
|
||||
| PRLinkMessage
|
||||
| FileHistorySnapshotMessage
|
||||
| AttributionSnapshotMessage
|
||||
| QueueOperationMessage
|
||||
| SpeculationAcceptMessage
|
||||
| ModeEntry
|
||||
| WorktreeStateEntry
|
||||
| ContentReplacementEntry
|
||||
| ContextCollapseCommitEntry
|
||||
| ContextCollapseSnapshotEntry
|
||||
|
||||
export function sortLogs(logs: LogOption[]): LogOption[] {
|
||||
return logs.sort((a, b) => {
|
||||
// Sort by modified date (newest first)
|
||||
const modifiedDiff = b.modified.getTime() - a.modified.getTime()
|
||||
if (modifiedDiff !== 0) {
|
||||
return modifiedDiff
|
||||
}
|
||||
|
||||
// If modified dates are equal, sort by created date (newest first)
|
||||
return b.created.getTime() - a.created.getTime()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
/**
|
||||
* Pure permission type definitions extracted to break import cycles.
|
||||
*
|
||||
* This file contains only type definitions and constants with no runtime dependencies.
|
||||
* Implementation files remain in src/utils/permissions/ but can now import from here
|
||||
* to avoid circular dependencies.
|
||||
*/
|
||||
|
||||
import { feature } from 'bun:bundle'
|
||||
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs'
|
||||
|
||||
// ============================================================================
|
||||
// Permission Modes
|
||||
// ============================================================================
|
||||
|
||||
export const EXTERNAL_PERMISSION_MODES = [
|
||||
'acceptEdits',
|
||||
'bypassPermissions',
|
||||
'default',
|
||||
'dontAsk',
|
||||
'plan',
|
||||
] as const
|
||||
|
||||
export type ExternalPermissionMode = (typeof EXTERNAL_PERMISSION_MODES)[number]
|
||||
|
||||
// Exhaustive mode union for typechecking. The user-addressable runtime set
|
||||
// is INTERNAL_PERMISSION_MODES below.
|
||||
export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'
|
||||
export type PermissionMode = InternalPermissionMode
|
||||
|
||||
// Runtime validation set: modes that are user-addressable (settings.json
|
||||
// defaultMode, --permission-mode CLI flag, conversation recovery).
|
||||
export const INTERNAL_PERMISSION_MODES = [
|
||||
...EXTERNAL_PERMISSION_MODES,
|
||||
...(feature('TRANSCRIPT_CLASSIFIER') ? (['auto'] as const) : ([] as const)),
|
||||
] as const satisfies readonly PermissionMode[]
|
||||
|
||||
export const PERMISSION_MODES = INTERNAL_PERMISSION_MODES
|
||||
|
||||
// ============================================================================
|
||||
// Permission Behaviors
|
||||
// ============================================================================
|
||||
|
||||
export type PermissionBehavior = 'allow' | 'deny' | 'ask'
|
||||
|
||||
// ============================================================================
|
||||
// Permission Rules
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Where a permission rule originated from.
|
||||
* Includes all SettingSource values plus additional rule-specific sources.
|
||||
*/
|
||||
export type PermissionRuleSource =
|
||||
| 'userSettings'
|
||||
| 'projectSettings'
|
||||
| 'localSettings'
|
||||
| 'flagSettings'
|
||||
| 'policySettings'
|
||||
| 'cliArg'
|
||||
| 'command'
|
||||
| 'session'
|
||||
|
||||
/**
|
||||
* The value of a permission rule - specifies which tool and optional content
|
||||
*/
|
||||
export type PermissionRuleValue = {
|
||||
toolName: string
|
||||
ruleContent?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A permission rule with its source and behavior
|
||||
*/
|
||||
export type PermissionRule = {
|
||||
source: PermissionRuleSource
|
||||
ruleBehavior: PermissionBehavior
|
||||
ruleValue: PermissionRuleValue
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Permission Updates
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Where a permission update should be persisted
|
||||
*/
|
||||
export type PermissionUpdateDestination =
|
||||
| 'userSettings'
|
||||
| 'projectSettings'
|
||||
| 'localSettings'
|
||||
| 'session'
|
||||
| 'cliArg'
|
||||
|
||||
/**
|
||||
* Update operations for permission configuration
|
||||
*/
|
||||
export type PermissionUpdate =
|
||||
| {
|
||||
type: 'addRules'
|
||||
destination: PermissionUpdateDestination
|
||||
rules: PermissionRuleValue[]
|
||||
behavior: PermissionBehavior
|
||||
}
|
||||
| {
|
||||
type: 'replaceRules'
|
||||
destination: PermissionUpdateDestination
|
||||
rules: PermissionRuleValue[]
|
||||
behavior: PermissionBehavior
|
||||
}
|
||||
| {
|
||||
type: 'removeRules'
|
||||
destination: PermissionUpdateDestination
|
||||
rules: PermissionRuleValue[]
|
||||
behavior: PermissionBehavior
|
||||
}
|
||||
| {
|
||||
type: 'setMode'
|
||||
destination: PermissionUpdateDestination
|
||||
mode: ExternalPermissionMode
|
||||
}
|
||||
| {
|
||||
type: 'addDirectories'
|
||||
destination: PermissionUpdateDestination
|
||||
directories: string[]
|
||||
}
|
||||
| {
|
||||
type: 'removeDirectories'
|
||||
destination: PermissionUpdateDestination
|
||||
directories: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Source of an additional working directory permission.
|
||||
* Note: This is currently the same as PermissionRuleSource but kept as a
|
||||
* separate type for semantic clarity and potential future divergence.
|
||||
*/
|
||||
export type WorkingDirectorySource = PermissionRuleSource
|
||||
|
||||
/**
|
||||
* An additional directory included in permission scope
|
||||
*/
|
||||
export type AdditionalWorkingDirectory = {
|
||||
path: string
|
||||
source: WorkingDirectorySource
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Permission Decisions & Results
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Minimal command shape for permission metadata.
|
||||
* This is intentionally a subset of the full Command type to avoid import cycles.
|
||||
* Only includes properties needed by permission-related components.
|
||||
*/
|
||||
export type PermissionCommandMetadata = {
|
||||
name: string
|
||||
description?: string
|
||||
// Allow additional properties for forward compatibility
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata attached to permission decisions
|
||||
*/
|
||||
export type PermissionMetadata =
|
||||
| { command: PermissionCommandMetadata }
|
||||
| undefined
|
||||
|
||||
/**
|
||||
* Result when permission is granted
|
||||
*/
|
||||
export type PermissionAllowDecision<
|
||||
Input extends { [key: string]: unknown } = { [key: string]: unknown },
|
||||
> = {
|
||||
behavior: 'allow'
|
||||
updatedInput?: Input
|
||||
userModified?: boolean
|
||||
decisionReason?: PermissionDecisionReason
|
||||
toolUseID?: string
|
||||
acceptFeedback?: string
|
||||
contentBlocks?: ContentBlockParam[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for a pending classifier check that will run asynchronously.
|
||||
* Used to enable non-blocking allow classifier evaluation.
|
||||
*/
|
||||
export type PendingClassifierCheck = {
|
||||
command: string
|
||||
cwd: string
|
||||
descriptions: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Result when user should be prompted
|
||||
*/
|
||||
export type PermissionAskDecision<
|
||||
Input extends { [key: string]: unknown } = { [key: string]: unknown },
|
||||
> = {
|
||||
behavior: 'ask'
|
||||
message: string
|
||||
updatedInput?: Input
|
||||
decisionReason?: PermissionDecisionReason
|
||||
suggestions?: PermissionUpdate[]
|
||||
blockedPath?: string
|
||||
metadata?: PermissionMetadata
|
||||
/**
|
||||
* If true, this ask decision was triggered by a bashCommandIsSafe_DEPRECATED security check
|
||||
* for patterns that splitCommand_DEPRECATED could misparse (e.g. line continuations, shell-quote
|
||||
* transformations). Used by bashToolHasPermission to block early before splitCommand_DEPRECATED
|
||||
* transforms the command. Not set for simple newline compound commands.
|
||||
*/
|
||||
isBashSecurityCheckForMisparsing?: boolean
|
||||
/**
|
||||
* If set, an allow classifier check should be run asynchronously.
|
||||
* The classifier may auto-approve the permission before the user responds.
|
||||
*/
|
||||
pendingClassifierCheck?: PendingClassifierCheck
|
||||
/**
|
||||
* Optional content blocks (e.g., images) to include alongside the rejection
|
||||
* message in the tool result. Used when users paste images as feedback.
|
||||
*/
|
||||
contentBlocks?: ContentBlockParam[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Result when permission is denied
|
||||
*/
|
||||
export type PermissionDenyDecision = {
|
||||
behavior: 'deny'
|
||||
message: string
|
||||
decisionReason: PermissionDecisionReason
|
||||
toolUseID?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A permission decision - allow, ask, or deny
|
||||
*/
|
||||
export type PermissionDecision<
|
||||
Input extends { [key: string]: unknown } = { [key: string]: unknown },
|
||||
> =
|
||||
| PermissionAllowDecision<Input>
|
||||
| PermissionAskDecision<Input>
|
||||
| PermissionDenyDecision
|
||||
|
||||
/**
|
||||
* Permission result with additional passthrough option
|
||||
*/
|
||||
export type PermissionResult<
|
||||
Input extends { [key: string]: unknown } = { [key: string]: unknown },
|
||||
> =
|
||||
| PermissionDecision<Input>
|
||||
| {
|
||||
behavior: 'passthrough'
|
||||
message: string
|
||||
decisionReason?: PermissionDecision<Input>['decisionReason']
|
||||
suggestions?: PermissionUpdate[]
|
||||
blockedPath?: string
|
||||
/**
|
||||
* If set, an allow classifier check should be run asynchronously.
|
||||
* The classifier may auto-approve the permission before the user responds.
|
||||
*/
|
||||
pendingClassifierCheck?: PendingClassifierCheck
|
||||
}
|
||||
|
||||
/**
|
||||
* Explanation of why a permission decision was made
|
||||
*/
|
||||
export type PermissionDecisionReason =
|
||||
| {
|
||||
type: 'rule'
|
||||
rule: PermissionRule
|
||||
}
|
||||
| {
|
||||
type: 'mode'
|
||||
mode: PermissionMode
|
||||
}
|
||||
| {
|
||||
type: 'subcommandResults'
|
||||
reasons: Map<string, PermissionResult>
|
||||
}
|
||||
| {
|
||||
type: 'permissionPromptTool'
|
||||
permissionPromptToolName: string
|
||||
toolResult: unknown
|
||||
}
|
||||
| {
|
||||
type: 'hook'
|
||||
hookName: string
|
||||
hookSource?: string
|
||||
reason?: string
|
||||
}
|
||||
| {
|
||||
type: 'asyncAgent'
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'sandboxOverride'
|
||||
reason: 'excludedCommand' | 'dangerouslyDisableSandbox'
|
||||
}
|
||||
| {
|
||||
type: 'classifier'
|
||||
classifier: string
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'workingDir'
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'safetyCheck'
|
||||
reason: string
|
||||
// When true, auto mode lets the classifier evaluate this instead of
|
||||
// forcing a prompt. True for sensitive-file paths (.claude/, .git/,
|
||||
// shell configs) — the classifier can see context and decide. False
|
||||
// for Windows path bypass attempts and cross-machine bridge messages.
|
||||
classifierApprovable: boolean
|
||||
}
|
||||
| {
|
||||
type: 'other'
|
||||
reason: string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Bash Classifier Types
|
||||
// ============================================================================
|
||||
|
||||
export type ClassifierResult = {
|
||||
matches: boolean
|
||||
matchedDescription?: string
|
||||
confidence: 'high' | 'medium' | 'low'
|
||||
reason: string
|
||||
}
|
||||
|
||||
export type ClassifierBehavior = 'deny' | 'ask' | 'allow'
|
||||
|
||||
export type ClassifierUsage = {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
cacheReadInputTokens: number
|
||||
cacheCreationInputTokens: number
|
||||
}
|
||||
|
||||
export type YoloClassifierResult = {
|
||||
thinking?: string
|
||||
shouldBlock: boolean
|
||||
reason: string
|
||||
unavailable?: boolean
|
||||
/**
|
||||
* API returned "prompt is too long" — the classifier transcript exceeded
|
||||
* the context window. Deterministic (same transcript → same error), so
|
||||
* callers should fall back to normal prompting rather than retry/fail-closed.
|
||||
*/
|
||||
transcriptTooLong?: boolean
|
||||
/** The model used for this classifier call */
|
||||
model: string
|
||||
/** Token usage from the classifier API call (for overhead telemetry) */
|
||||
usage?: ClassifierUsage
|
||||
/** Duration of the classifier API call in ms */
|
||||
durationMs?: number
|
||||
/** Character lengths of the prompt components sent to the classifier */
|
||||
promptLengths?: {
|
||||
systemPrompt: number
|
||||
toolCalls: number
|
||||
userPrompts: number
|
||||
}
|
||||
/** Path where error prompts were dumped (only set when unavailable due to API error) */
|
||||
errorDumpPath?: string
|
||||
/** Which classifier stage produced the final decision (2-stage XML only) */
|
||||
stage?: 'fast' | 'thinking'
|
||||
/** Token usage from stage 1 (fast) when stage 2 was also run */
|
||||
stage1Usage?: ClassifierUsage
|
||||
/** Duration of stage 1 in ms when stage 2 was also run */
|
||||
stage1DurationMs?: number
|
||||
/**
|
||||
* API request_id (req_xxx) for stage 1. Enables joining to server-side
|
||||
* api_usage logs for cache-miss / routing attribution. Also used for the
|
||||
* legacy 1-stage (tool_use) classifier — the single request goes here.
|
||||
*/
|
||||
stage1RequestId?: string
|
||||
/**
|
||||
* API message id (msg_xxx) for stage 1. Enables joining the
|
||||
* tengu_auto_mode_decision analytics event to the classifier's actual
|
||||
* prompt/completion in post-analysis.
|
||||
*/
|
||||
stage1MsgId?: string
|
||||
/** Token usage from stage 2 (thinking) when stage 2 was run */
|
||||
stage2Usage?: ClassifierUsage
|
||||
/** Duration of stage 2 in ms when stage 2 was run */
|
||||
stage2DurationMs?: number
|
||||
/** API request_id for stage 2 (set whenever stage 2 ran) */
|
||||
stage2RequestId?: string
|
||||
/** API message id (msg_xxx) for stage 2 (set whenever stage 2 ran) */
|
||||
stage2MsgId?: string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Permission Explainer Types
|
||||
// ============================================================================
|
||||
|
||||
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
|
||||
export type PermissionExplanation = {
|
||||
riskLevel: RiskLevel
|
||||
explanation: string
|
||||
reasoning: string
|
||||
risk: string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tool Permission Context
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Mapping of permission rules by their source
|
||||
*/
|
||||
export type ToolPermissionRulesBySource = {
|
||||
[T in PermissionRuleSource]?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Context needed for permission checking in tools
|
||||
* Note: Uses a simplified DeepImmutable approximation for this types-only file
|
||||
*/
|
||||
export type ToolPermissionContext = {
|
||||
readonly mode: PermissionMode
|
||||
readonly additionalWorkingDirectories: ReadonlyMap<
|
||||
string,
|
||||
AdditionalWorkingDirectory
|
||||
>
|
||||
readonly alwaysAllowRules: ToolPermissionRulesBySource
|
||||
readonly alwaysDenyRules: ToolPermissionRulesBySource
|
||||
readonly alwaysAskRules: ToolPermissionRulesBySource
|
||||
readonly isBypassPermissionsModeAvailable: boolean
|
||||
readonly strippedDangerousRules?: ToolPermissionRulesBySource
|
||||
readonly shouldAvoidPermissionPrompts?: boolean
|
||||
readonly awaitAutomatedChecksBeforeDialog?: boolean
|
||||
readonly prePlanMode?: PermissionMode
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
import type { LspServerConfig } from '../services/lsp/types.js'
|
||||
import type { McpServerConfig } from '../services/mcp/types.js'
|
||||
import type { BundledSkillDefinition } from '../skills/bundledSkills.js'
|
||||
import type {
|
||||
CommandMetadata,
|
||||
PluginAuthor,
|
||||
PluginManifest,
|
||||
} from '../utils/plugins/schemas.js'
|
||||
import type { HooksSettings } from '../utils/settings/types.js'
|
||||
|
||||
export type { PluginAuthor, PluginManifest, CommandMetadata }
|
||||
|
||||
/**
|
||||
* Definition for a built-in plugin that ships with the CLI.
|
||||
* Built-in plugins appear in the /plugin UI and can be enabled/disabled by
|
||||
* users (persisted to user settings).
|
||||
*/
|
||||
export type BuiltinPluginDefinition = {
|
||||
/** Plugin name (used in `{name}@builtin` identifier) */
|
||||
name: string
|
||||
/** Description shown in the /plugin UI */
|
||||
description: string
|
||||
/** Optional version string */
|
||||
version?: string
|
||||
/** Skills provided by this plugin */
|
||||
skills?: BundledSkillDefinition[]
|
||||
/** Hooks provided by this plugin */
|
||||
hooks?: HooksSettings
|
||||
/** MCP servers provided by this plugin */
|
||||
mcpServers?: Record<string, McpServerConfig>
|
||||
/** Whether this plugin is available (e.g. based on system capabilities). Unavailable plugins are hidden entirely. */
|
||||
isAvailable?: () => boolean
|
||||
/** Default enabled state before the user sets a preference (defaults to true) */
|
||||
defaultEnabled?: boolean
|
||||
}
|
||||
|
||||
export type PluginRepository = {
|
||||
url: string
|
||||
branch: string
|
||||
lastUpdated?: string
|
||||
commitSha?: string
|
||||
}
|
||||
|
||||
export type PluginConfig = {
|
||||
repositories: Record<string, PluginRepository>
|
||||
}
|
||||
|
||||
export type LoadedPlugin = {
|
||||
name: string
|
||||
manifest: PluginManifest
|
||||
path: string
|
||||
source: string
|
||||
repository: string // Repository identifier, usually same as source
|
||||
enabled?: boolean
|
||||
isBuiltin?: boolean // true for built-in plugins that ship with the CLI
|
||||
sha?: string // Git commit SHA for version pinning (from marketplace entry source)
|
||||
commandsPath?: string
|
||||
commandsPaths?: string[] // Additional command paths from manifest
|
||||
commandsMetadata?: Record<string, CommandMetadata> // Metadata for named commands from object-mapping format
|
||||
agentsPath?: string
|
||||
agentsPaths?: string[] // Additional agent paths from manifest
|
||||
skillsPath?: string
|
||||
skillsPaths?: string[] // Additional skill paths from manifest
|
||||
outputStylesPath?: string
|
||||
outputStylesPaths?: string[] // Additional output style paths from manifest
|
||||
hooksConfig?: HooksSettings
|
||||
mcpServers?: Record<string, McpServerConfig>
|
||||
lspServers?: Record<string, LspServerConfig>
|
||||
settings?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type PluginComponent =
|
||||
| 'commands'
|
||||
| 'agents'
|
||||
| 'skills'
|
||||
| 'hooks'
|
||||
| 'output-styles'
|
||||
|
||||
/**
|
||||
* Discriminated union of plugin error types.
|
||||
* Each error type has specific contextual data for better debugging and user guidance.
|
||||
*
|
||||
* This replaces the previous string-based error matching approach with type-safe
|
||||
* error handling that can't break when error messages change.
|
||||
*
|
||||
* IMPLEMENTATION STATUS:
|
||||
* Currently used in production (2 types):
|
||||
* - generic-error: Used for various plugin loading failures
|
||||
* - plugin-not-found: Used when plugin not found in marketplace
|
||||
*
|
||||
* Planned for future use (10 types - see TODOs in pluginLoader.ts):
|
||||
* - path-not-found, git-auth-failed, git-timeout, network-error
|
||||
* - manifest-parse-error, manifest-validation-error
|
||||
* - marketplace-not-found, marketplace-load-failed
|
||||
* - mcp-config-invalid, hook-load-failed, component-load-failed
|
||||
*
|
||||
* These unused types support UI formatting and provide a clear roadmap for
|
||||
* improving error specificity. They can be incrementally implemented as
|
||||
* error creation sites are refactored.
|
||||
*/
|
||||
export type PluginError =
|
||||
| {
|
||||
type: 'path-not-found'
|
||||
source: string
|
||||
plugin?: string
|
||||
path: string
|
||||
component: PluginComponent
|
||||
}
|
||||
| {
|
||||
type: 'git-auth-failed'
|
||||
source: string
|
||||
plugin?: string
|
||||
gitUrl: string
|
||||
authType: 'ssh' | 'https'
|
||||
}
|
||||
| {
|
||||
type: 'git-timeout'
|
||||
source: string
|
||||
plugin?: string
|
||||
gitUrl: string
|
||||
operation: 'clone' | 'pull'
|
||||
}
|
||||
| {
|
||||
type: 'network-error'
|
||||
source: string
|
||||
plugin?: string
|
||||
url: string
|
||||
details?: string
|
||||
}
|
||||
| {
|
||||
type: 'manifest-parse-error'
|
||||
source: string
|
||||
plugin?: string
|
||||
manifestPath: string
|
||||
parseError: string
|
||||
}
|
||||
| {
|
||||
type: 'manifest-validation-error'
|
||||
source: string
|
||||
plugin?: string
|
||||
manifestPath: string
|
||||
validationErrors: string[]
|
||||
}
|
||||
| {
|
||||
type: 'plugin-not-found'
|
||||
source: string
|
||||
pluginId: string
|
||||
marketplace: string
|
||||
}
|
||||
| {
|
||||
type: 'marketplace-not-found'
|
||||
source: string
|
||||
marketplace: string
|
||||
availableMarketplaces: string[]
|
||||
}
|
||||
| {
|
||||
type: 'marketplace-load-failed'
|
||||
source: string
|
||||
marketplace: string
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'mcp-config-invalid'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
validationError: string
|
||||
}
|
||||
| {
|
||||
type: 'mcp-server-suppressed-duplicate'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
duplicateOf: string
|
||||
}
|
||||
| {
|
||||
type: 'lsp-config-invalid'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
validationError: string
|
||||
}
|
||||
| {
|
||||
type: 'hook-load-failed'
|
||||
source: string
|
||||
plugin: string
|
||||
hookPath: string
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'component-load-failed'
|
||||
source: string
|
||||
plugin: string
|
||||
component: PluginComponent
|
||||
path: string
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'mcpb-download-failed'
|
||||
source: string
|
||||
plugin: string
|
||||
url: string
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'mcpb-extract-failed'
|
||||
source: string
|
||||
plugin: string
|
||||
mcpbPath: string
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'mcpb-invalid-manifest'
|
||||
source: string
|
||||
plugin: string
|
||||
mcpbPath: string
|
||||
validationError: string
|
||||
}
|
||||
| {
|
||||
type: 'lsp-config-invalid'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
validationError: string
|
||||
}
|
||||
| {
|
||||
type: 'lsp-server-start-failed'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
reason: string
|
||||
}
|
||||
| {
|
||||
type: 'lsp-server-crashed'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
exitCode: number | null
|
||||
signal?: string
|
||||
}
|
||||
| {
|
||||
type: 'lsp-request-timeout'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
method: string
|
||||
timeoutMs: number
|
||||
}
|
||||
| {
|
||||
type: 'lsp-request-failed'
|
||||
source: string
|
||||
plugin: string
|
||||
serverName: string
|
||||
method: string
|
||||
error: string
|
||||
}
|
||||
| {
|
||||
type: 'marketplace-blocked-by-policy'
|
||||
source: string
|
||||
plugin?: string
|
||||
marketplace: string
|
||||
blockedByBlocklist?: boolean // true if blocked by blockedMarketplaces, false if not in strictKnownMarketplaces
|
||||
allowedSources: string[] // Formatted source strings (e.g., "github:owner/repo")
|
||||
}
|
||||
| {
|
||||
type: 'dependency-unsatisfied'
|
||||
source: string
|
||||
plugin: string
|
||||
dependency: string
|
||||
reason: 'not-enabled' | 'not-found'
|
||||
}
|
||||
| {
|
||||
type: 'plugin-cache-miss'
|
||||
source: string
|
||||
plugin: string
|
||||
installPath: string
|
||||
}
|
||||
| {
|
||||
type: 'generic-error'
|
||||
source: string
|
||||
plugin?: string
|
||||
error: string
|
||||
}
|
||||
|
||||
export type PluginLoadResult = {
|
||||
enabled: LoadedPlugin[]
|
||||
disabled: LoadedPlugin[]
|
||||
errors: PluginError[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a display message from any PluginError
|
||||
* Useful for logging and simple error displays
|
||||
*/
|
||||
export function getPluginErrorMessage(error: PluginError): string {
|
||||
switch (error.type) {
|
||||
case 'generic-error':
|
||||
return error.error
|
||||
case 'path-not-found':
|
||||
return `Path not found: ${error.path} (${error.component})`
|
||||
case 'git-auth-failed':
|
||||
return `Git authentication failed (${error.authType}): ${error.gitUrl}`
|
||||
case 'git-timeout':
|
||||
return `Git ${error.operation} timeout: ${error.gitUrl}`
|
||||
case 'network-error':
|
||||
return `Network error: ${error.url}${error.details ? ` - ${error.details}` : ''}`
|
||||
case 'manifest-parse-error':
|
||||
return `Manifest parse error: ${error.parseError}`
|
||||
case 'manifest-validation-error':
|
||||
return `Manifest validation failed: ${error.validationErrors.join(', ')}`
|
||||
case 'plugin-not-found':
|
||||
return `Plugin ${error.pluginId} not found in marketplace ${error.marketplace}`
|
||||
case 'marketplace-not-found':
|
||||
return `Marketplace ${error.marketplace} not found`
|
||||
case 'marketplace-load-failed':
|
||||
return `Marketplace ${error.marketplace} failed to load: ${error.reason}`
|
||||
case 'mcp-config-invalid':
|
||||
return `MCP server ${error.serverName} invalid: ${error.validationError}`
|
||||
case 'mcp-server-suppressed-duplicate': {
|
||||
const dup = error.duplicateOf.startsWith('plugin:')
|
||||
? `server provided by plugin "${error.duplicateOf.split(':')[1] ?? '?'}"`
|
||||
: `already-configured "${error.duplicateOf}"`
|
||||
return `MCP server "${error.serverName}" skipped — same command/URL as ${dup}`
|
||||
}
|
||||
case 'hook-load-failed':
|
||||
return `Hook load failed: ${error.reason}`
|
||||
case 'component-load-failed':
|
||||
return `${error.component} load failed from ${error.path}: ${error.reason}`
|
||||
case 'mcpb-download-failed':
|
||||
return `Failed to download MCPB from ${error.url}: ${error.reason}`
|
||||
case 'mcpb-extract-failed':
|
||||
return `Failed to extract MCPB ${error.mcpbPath}: ${error.reason}`
|
||||
case 'mcpb-invalid-manifest':
|
||||
return `MCPB manifest invalid at ${error.mcpbPath}: ${error.validationError}`
|
||||
case 'lsp-config-invalid':
|
||||
return `Plugin "${error.plugin}" has invalid LSP server config for "${error.serverName}": ${error.validationError}`
|
||||
case 'lsp-server-start-failed':
|
||||
return `Plugin "${error.plugin}" failed to start LSP server "${error.serverName}": ${error.reason}`
|
||||
case 'lsp-server-crashed':
|
||||
if (error.signal) {
|
||||
return `Plugin "${error.plugin}" LSP server "${error.serverName}" crashed with signal ${error.signal}`
|
||||
}
|
||||
return `Plugin "${error.plugin}" LSP server "${error.serverName}" crashed with exit code ${error.exitCode ?? 'unknown'}`
|
||||
case 'lsp-request-timeout':
|
||||
return `Plugin "${error.plugin}" LSP server "${error.serverName}" timed out on ${error.method} request after ${error.timeoutMs}ms`
|
||||
case 'lsp-request-failed':
|
||||
return `Plugin "${error.plugin}" LSP server "${error.serverName}" ${error.method} request failed: ${error.error}`
|
||||
case 'marketplace-blocked-by-policy':
|
||||
if (error.blockedByBlocklist) {
|
||||
return `Marketplace '${error.marketplace}' is blocked by enterprise policy`
|
||||
}
|
||||
return `Marketplace '${error.marketplace}' is not in the allowed marketplace list`
|
||||
case 'dependency-unsatisfied': {
|
||||
const hint =
|
||||
error.reason === 'not-enabled'
|
||||
? 'disabled — enable it or remove the dependency'
|
||||
: 'not found in any configured marketplace'
|
||||
return `Dependency "${error.dependency}" is ${hint}`
|
||||
}
|
||||
case 'plugin-cache-miss':
|
||||
return `Plugin "${error.plugin}" not cached at ${error.installPath} — run /plugins to refresh`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs'
|
||||
import type { UUID } from 'crypto'
|
||||
import type React from 'react'
|
||||
import type { PermissionResult } from '../entrypoints/agentSdkTypes.js'
|
||||
import type { Key } from '../ink.js'
|
||||
import type { PastedContent } from '../utils/config.js'
|
||||
import type { ImageDimensions } from '../utils/imageResizer.js'
|
||||
import type { TextHighlight } from '../utils/textHighlighting.js'
|
||||
import type { AgentId } from './ids.js'
|
||||
import type { AssistantMessage, MessageOrigin } from './message.js'
|
||||
|
||||
/**
|
||||
* Inline ghost text for mid-input command autocomplete
|
||||
*/
|
||||
export type InlineGhostText = {
|
||||
/** The ghost text to display (e.g., "mit" for /commit) */
|
||||
readonly text: string
|
||||
/** The full command name (e.g., "commit") */
|
||||
readonly fullCommand: string
|
||||
/** Position in the input where the ghost text should appear */
|
||||
readonly insertPosition: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Base props for text input components
|
||||
*/
|
||||
export type BaseTextInputProps = {
|
||||
/**
|
||||
* Optional callback for handling history navigation on up arrow at start of input
|
||||
*/
|
||||
readonly onHistoryUp?: () => void
|
||||
|
||||
/**
|
||||
* Optional callback for handling history navigation on down arrow at end of input
|
||||
*/
|
||||
readonly onHistoryDown?: () => void
|
||||
|
||||
/**
|
||||
* Text to display when `value` is empty.
|
||||
*/
|
||||
readonly placeholder?: string
|
||||
|
||||
/**
|
||||
* Allow multi-line input via line ending with backslash (default: `true`)
|
||||
*/
|
||||
readonly multiline?: boolean
|
||||
|
||||
/**
|
||||
* Listen to user's input. Useful in case there are multiple input components
|
||||
* at the same time and input must be "routed" to a specific component.
|
||||
*/
|
||||
readonly focus?: boolean
|
||||
|
||||
/**
|
||||
* Replace all chars and mask the value. Useful for password inputs.
|
||||
*/
|
||||
readonly mask?: string
|
||||
|
||||
/**
|
||||
* Whether to show cursor and allow navigation inside text input with arrow keys.
|
||||
*/
|
||||
readonly showCursor?: boolean
|
||||
|
||||
/**
|
||||
* Highlight pasted text
|
||||
*/
|
||||
readonly highlightPastedText?: boolean
|
||||
|
||||
/**
|
||||
* Value to display in a text input.
|
||||
*/
|
||||
readonly value: string
|
||||
|
||||
/**
|
||||
* Function to call when value updates.
|
||||
*/
|
||||
readonly onChange: (value: string) => void
|
||||
|
||||
/**
|
||||
* Function to call when `Enter` is pressed, where first argument is a value of the input.
|
||||
*/
|
||||
readonly onSubmit?: (value: string) => void
|
||||
|
||||
/**
|
||||
* Function to call when Ctrl+C is pressed to exit.
|
||||
*/
|
||||
readonly onExit?: () => void
|
||||
|
||||
/**
|
||||
* Optional callback to show exit message
|
||||
*/
|
||||
readonly onExitMessage?: (show: boolean, key?: string) => void
|
||||
|
||||
/**
|
||||
* Optional callback to show custom message
|
||||
*/
|
||||
// readonly onMessage?: (show: boolean, message?: string) => void
|
||||
|
||||
/**
|
||||
* Optional callback to reset history position
|
||||
*/
|
||||
readonly onHistoryReset?: () => void
|
||||
|
||||
/**
|
||||
* Optional callback when input is cleared (e.g., double-escape)
|
||||
*/
|
||||
readonly onClearInput?: () => void
|
||||
|
||||
/**
|
||||
* Number of columns to wrap text at
|
||||
*/
|
||||
readonly columns: number
|
||||
|
||||
/**
|
||||
* Maximum visible lines for the input viewport. When the wrapped input
|
||||
* exceeds this many lines, only lines around the cursor are rendered.
|
||||
*/
|
||||
readonly maxVisibleLines?: number
|
||||
|
||||
/**
|
||||
* Optional callback when an image is pasted
|
||||
*/
|
||||
readonly onImagePaste?: (
|
||||
base64Image: string,
|
||||
mediaType?: string,
|
||||
filename?: string,
|
||||
dimensions?: ImageDimensions,
|
||||
sourcePath?: string,
|
||||
) => void
|
||||
|
||||
/**
|
||||
* Optional callback when a large text (over 800 chars) is pasted
|
||||
*/
|
||||
readonly onPaste?: (text: string) => void
|
||||
|
||||
/**
|
||||
* Callback when the pasting state changes
|
||||
*/
|
||||
readonly onIsPastingChange?: (isPasting: boolean) => void
|
||||
|
||||
/**
|
||||
* Whether to disable cursor movement for up/down arrow keys
|
||||
*/
|
||||
readonly disableCursorMovementForUpDownKeys?: boolean
|
||||
|
||||
/**
|
||||
* Skip the text-level double-press escape handler. Set this when a
|
||||
* keybinding context (e.g. Autocomplete) owns escape — the keybinding's
|
||||
* stopImmediatePropagation can't shield the text input because child
|
||||
* effects register useInput listeners before parent effects.
|
||||
*/
|
||||
readonly disableEscapeDoublePress?: boolean
|
||||
|
||||
/**
|
||||
* The offset of the cursor within the text
|
||||
*/
|
||||
readonly cursorOffset: number
|
||||
|
||||
/**
|
||||
* Callback to set the offset of the cursor
|
||||
*/
|
||||
onChangeCursorOffset: (offset: number) => void
|
||||
|
||||
/**
|
||||
* Optional hint text to display after command input
|
||||
* Used for showing available arguments for commands
|
||||
*/
|
||||
readonly argumentHint?: string
|
||||
|
||||
/**
|
||||
* Optional callback for undo functionality
|
||||
*/
|
||||
readonly onUndo?: () => void
|
||||
|
||||
/**
|
||||
* Whether to render the text with dim color
|
||||
*/
|
||||
readonly dimColor?: boolean
|
||||
|
||||
/**
|
||||
* Optional text highlights for search results or other highlighting
|
||||
*/
|
||||
readonly highlights?: TextHighlight[]
|
||||
|
||||
/**
|
||||
* Optional custom React element to render as placeholder.
|
||||
* When provided, overrides the standard `placeholder` string rendering.
|
||||
*/
|
||||
readonly placeholderElement?: React.ReactNode
|
||||
|
||||
/**
|
||||
* Optional inline ghost text for mid-input command autocomplete
|
||||
*/
|
||||
readonly inlineGhostText?: InlineGhostText
|
||||
|
||||
/**
|
||||
* Optional filter applied to raw input before key routing. Return the
|
||||
* (possibly transformed) input string; returning '' for a non-empty
|
||||
* input drops the event.
|
||||
*/
|
||||
readonly inputFilter?: (input: string, key: Key) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended props for VimTextInput
|
||||
*/
|
||||
export type VimTextInputProps = BaseTextInputProps & {
|
||||
/**
|
||||
* Initial vim mode to use
|
||||
*/
|
||||
readonly initialMode?: VimMode
|
||||
|
||||
/**
|
||||
* Optional callback for mode changes
|
||||
*/
|
||||
readonly onModeChange?: (mode: VimMode) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Vim editor modes
|
||||
*/
|
||||
export type VimMode = 'INSERT' | 'NORMAL'
|
||||
|
||||
/**
|
||||
* Common properties for input hook results
|
||||
*/
|
||||
export type BaseInputState = {
|
||||
onInput: (input: string, key: Key) => void
|
||||
renderedValue: string
|
||||
offset: number
|
||||
setOffset: (offset: number) => void
|
||||
/** Cursor line (0-indexed) within the rendered text, accounting for wrapping. */
|
||||
cursorLine: number
|
||||
/** Cursor column (display-width) within the current line. */
|
||||
cursorColumn: number
|
||||
/** Character offset in the full text where the viewport starts (0 when no windowing). */
|
||||
viewportCharOffset: number
|
||||
/** Character offset in the full text where the viewport ends (text.length when no windowing). */
|
||||
viewportCharEnd: number
|
||||
|
||||
// For paste handling
|
||||
isPasting?: boolean
|
||||
pasteState?: {
|
||||
chunks: string[]
|
||||
timeoutId: ReturnType<typeof setTimeout> | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State for text input
|
||||
*/
|
||||
export type TextInputState = BaseInputState
|
||||
|
||||
/**
|
||||
* State for vim input with mode
|
||||
*/
|
||||
export type VimInputState = BaseInputState & {
|
||||
mode: VimMode
|
||||
setMode: (mode: VimMode) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Input modes for the prompt
|
||||
*/
|
||||
export type PromptInputMode =
|
||||
| 'bash'
|
||||
| 'prompt'
|
||||
| 'orphaned-permission'
|
||||
| 'task-notification'
|
||||
|
||||
export type EditablePromptInputMode = Exclude<
|
||||
PromptInputMode,
|
||||
`${string}-notification`
|
||||
>
|
||||
|
||||
/**
|
||||
* Queue priority levels. Same semantics in both normal and proactive mode.
|
||||
*
|
||||
* - `now` — Interrupt and send immediately. Aborts any in-flight tool
|
||||
* call (equivalent to Esc + send). Consumers (print.ts,
|
||||
* REPL.tsx) subscribe to queue changes and abort when they
|
||||
* see a 'now' command.
|
||||
* - `next` — Mid-turn drain. Let the current tool call finish, then
|
||||
* send this message between the tool result and the next API
|
||||
* round-trip. Wakes an in-progress SleepTool call.
|
||||
* - `later` — End-of-turn drain. Wait for the current turn to finish,
|
||||
* then process as a new query. Wakes an in-progress SleepTool
|
||||
* call (query.ts upgrades the drain threshold after sleep so
|
||||
* the message is attached to the same turn).
|
||||
*
|
||||
* The SleepTool is only available in proactive mode, so "wakes SleepTool"
|
||||
* is a no-op in normal mode.
|
||||
*/
|
||||
export type QueuePriority = 'now' | 'next' | 'later'
|
||||
|
||||
/**
|
||||
* Queued command type
|
||||
*/
|
||||
export type QueuedCommand = {
|
||||
value: string | Array<ContentBlockParam>
|
||||
mode: PromptInputMode
|
||||
/** Defaults to the priority implied by `mode` when enqueued. */
|
||||
priority?: QueuePriority
|
||||
uuid?: UUID
|
||||
orphanedPermission?: OrphanedPermission
|
||||
/** Raw pasted contents including images. Images are resized at execution time. */
|
||||
pastedContents?: Record<number, PastedContent>
|
||||
/**
|
||||
* The input string before [Pasted text #N] placeholders were expanded.
|
||||
* Used for ultraplan keyword detection so pasted content containing the
|
||||
* keyword does not trigger a CCR session. Falls back to `value` when
|
||||
* unset (bridge/UDS/MCP sources have no paste expansion).
|
||||
*/
|
||||
preExpansionValue?: string
|
||||
/**
|
||||
* When true, the input is treated as plain text even if it starts with `/`.
|
||||
* Used for remotely-received messages (e.g. bridge/CCR) that should not
|
||||
* trigger local slash commands or skills.
|
||||
*/
|
||||
skipSlashCommands?: boolean
|
||||
/**
|
||||
* When true, slash commands are dispatched but filtered through
|
||||
* isBridgeSafeCommand() — 'local-jsx' and terminal-only commands return
|
||||
* a helpful error instead of executing. Set by the Remote Control bridge
|
||||
* inbound path so mobile/web clients can run skills and benign commands
|
||||
* without re-exposing the PR #19134 bug (/model popping the local picker).
|
||||
*/
|
||||
bridgeOrigin?: boolean
|
||||
/**
|
||||
* When true, the resulting UserMessage gets `isMeta: true` — hidden in the
|
||||
* transcript UI but visible to the model. Used by system-generated prompts
|
||||
* (proactive ticks, teammate messages, resource updates) that route through
|
||||
* the queue instead of calling `onQuery` directly.
|
||||
*/
|
||||
isMeta?: boolean
|
||||
/**
|
||||
* Provenance of this command. Stamped onto the resulting UserMessage so the
|
||||
* transcript records origin structurally (not just via XML tags in content).
|
||||
* undefined = human (keyboard).
|
||||
*/
|
||||
origin?: MessageOrigin
|
||||
/**
|
||||
* Workload tag threaded through to cc_workload= in the billing-header
|
||||
* attribution block. The queue is the async boundary between the cron
|
||||
* scheduler firing and the turn actually running — a user prompt can slip
|
||||
* in between — so the tag rides on the QueuedCommand itself and is only
|
||||
* hoisted into bootstrap state when THIS command is dequeued.
|
||||
*/
|
||||
workload?: string
|
||||
/**
|
||||
* Agent that should receive this notification. Undefined = main thread.
|
||||
* Subagents run in-process and share the module-level command queue; the
|
||||
* drain gate in query.ts filters by this field so a subagent's background
|
||||
* task notifications don't leak into the coordinator's context (PR #18453
|
||||
* unified the queue but lost the isolation the dual-queue accidentally had).
|
||||
*/
|
||||
agentId?: AgentId
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for image PastedContent with non-empty data. Empty-content
|
||||
* images (e.g. from a 0-byte file drag) yield empty base64 strings that
|
||||
* the API rejects with `image cannot be empty`. Use this at every site
|
||||
* that converts PastedContent → ImageBlockParam so the filter and the
|
||||
* ID list stay in sync.
|
||||
*/
|
||||
export function isValidImagePaste(c: PastedContent): boolean {
|
||||
return c.type === 'image' && c.content.length > 0
|
||||
}
|
||||
|
||||
/** Extract image paste IDs from a QueuedCommand's pastedContents. */
|
||||
export function getImagePasteIds(
|
||||
pastedContents: Record<number, PastedContent> | undefined,
|
||||
): number[] | undefined {
|
||||
if (!pastedContents) {
|
||||
return undefined
|
||||
}
|
||||
const ids = Object.values(pastedContents)
|
||||
.filter(isValidImagePaste)
|
||||
.map(c => c.id)
|
||||
return ids.length > 0 ? ids : undefined
|
||||
}
|
||||
|
||||
export type OrphanedPermission = {
|
||||
permissionResult: PermissionResult
|
||||
assistantMessage: AssistantMessage
|
||||
}
|
||||
Reference in New Issue
Block a user