init claude-code
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* Teleported /ultrareview execution. Creates a CCR session with the current repo,
|
||||
* sends the review prompt as the initial message, and registers a
|
||||
* RemoteAgentTask so the polling loop pipes results back into the local
|
||||
* session via task-notification. Mirrors the /ultraplan → CCR flow.
|
||||
*
|
||||
* TODO(#22051): pass useBundleMode once landed so local-only / uncommitted
|
||||
* repo state is captured. The GitHub-clone path (current) only works for
|
||||
* pushed branches on repos with the Claude GitHub app installed.
|
||||
*/
|
||||
|
||||
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.js'
|
||||
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
|
||||
import {
|
||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
logEvent,
|
||||
} from '../../services/analytics/index.js'
|
||||
import { fetchUltrareviewQuota } from '../../services/api/ultrareviewQuota.js'
|
||||
import { fetchUtilization } from '../../services/api/usage.js'
|
||||
import type { ToolUseContext } from '../../Tool.js'
|
||||
import {
|
||||
checkRemoteAgentEligibility,
|
||||
formatPreconditionError,
|
||||
getRemoteTaskSessionUrl,
|
||||
registerRemoteAgentTask,
|
||||
} from '../../tasks/RemoteAgentTask/RemoteAgentTask.js'
|
||||
import { isEnterpriseSubscriber, isTeamSubscriber } from '../../utils/auth.js'
|
||||
import { detectCurrentRepositoryWithHost } from '../../utils/detectRepository.js'
|
||||
import { execFileNoThrow } from '../../utils/execFileNoThrow.js'
|
||||
import { getDefaultBranch, gitExe } from '../../utils/git.js'
|
||||
import { teleportToRemote } from '../../utils/teleport.js'
|
||||
|
||||
// One-time session flag: once the user confirms overage billing via the
|
||||
// dialog, all subsequent /ultrareview invocations in this session proceed
|
||||
// without re-prompting.
|
||||
let sessionOverageConfirmed = false
|
||||
|
||||
export function confirmOverage(): void {
|
||||
sessionOverageConfirmed = true
|
||||
}
|
||||
|
||||
export type OverageGate =
|
||||
| { kind: 'proceed'; billingNote: string }
|
||||
| { kind: 'not-enabled' }
|
||||
| { kind: 'low-balance'; available: number }
|
||||
| { kind: 'needs-confirm' }
|
||||
|
||||
/**
|
||||
* Determine whether the user can launch an ultrareview and under what
|
||||
* billing terms. Fetches quota and utilization in parallel.
|
||||
*/
|
||||
export async function checkOverageGate(): Promise<OverageGate> {
|
||||
// Team and Enterprise plans include ultrareview — no free-review quota
|
||||
// or Extra Usage dialog. The quota endpoint is scoped to consumer plans
|
||||
// (pro/max); hitting it on team/ent would surface a confusing dialog.
|
||||
if (isTeamSubscriber() || isEnterpriseSubscriber()) {
|
||||
return { kind: 'proceed', billingNote: '' }
|
||||
}
|
||||
|
||||
const [quota, utilization] = await Promise.all([
|
||||
fetchUltrareviewQuota(),
|
||||
fetchUtilization().catch(() => null),
|
||||
])
|
||||
|
||||
// No quota info (non-subscriber or endpoint down) — let it through,
|
||||
// server-side billing will handle it.
|
||||
if (!quota) {
|
||||
return { kind: 'proceed', billingNote: '' }
|
||||
}
|
||||
|
||||
if (quota.reviews_remaining > 0) {
|
||||
return {
|
||||
kind: 'proceed',
|
||||
billingNote: ` This is free ultrareview ${quota.reviews_used + 1} of ${quota.reviews_limit}.`,
|
||||
}
|
||||
}
|
||||
|
||||
// Utilization fetch failed (transient network error, timeout, etc.) —
|
||||
// let it through, same rationale as the quota fallback above.
|
||||
if (!utilization) {
|
||||
return { kind: 'proceed', billingNote: '' }
|
||||
}
|
||||
|
||||
// Free reviews exhausted — check Extra Usage setup.
|
||||
const extraUsage = utilization.extra_usage
|
||||
if (!extraUsage?.is_enabled) {
|
||||
logEvent('tengu_review_overage_not_enabled', {})
|
||||
return { kind: 'not-enabled' }
|
||||
}
|
||||
|
||||
// Check available balance (null monthly_limit = unlimited).
|
||||
const monthlyLimit = extraUsage.monthly_limit
|
||||
const usedCredits = extraUsage.used_credits ?? 0
|
||||
const available =
|
||||
monthlyLimit === null || monthlyLimit === undefined
|
||||
? Infinity
|
||||
: monthlyLimit - usedCredits
|
||||
|
||||
if (available < 10) {
|
||||
logEvent('tengu_review_overage_low_balance', { available })
|
||||
return { kind: 'low-balance', available }
|
||||
}
|
||||
|
||||
if (!sessionOverageConfirmed) {
|
||||
logEvent('tengu_review_overage_dialog_shown', {})
|
||||
return { kind: 'needs-confirm' }
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'proceed',
|
||||
billingNote: ' This review bills as Extra Usage.',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a teleported review session. Returns ContentBlockParam[] describing
|
||||
* the launch outcome for injection into the local conversation (model is then
|
||||
* queried with this content, so it can narrate the launch to the user).
|
||||
*
|
||||
* Returns ContentBlockParam[] with user-facing error messages on recoverable
|
||||
* failures (missing merge-base, empty diff, bundle too large), or null on
|
||||
* other failures so the caller falls through to the local-review prompt.
|
||||
* Reason is captured in analytics.
|
||||
*
|
||||
* Caller must run checkOverageGate() BEFORE calling this function
|
||||
* (ultrareviewCommand.tsx handles the dialog).
|
||||
*/
|
||||
export async function launchRemoteReview(
|
||||
args: string,
|
||||
context: ToolUseContext,
|
||||
billingNote?: string,
|
||||
): Promise<ContentBlockParam[] | null> {
|
||||
const eligibility = await checkRemoteAgentEligibility()
|
||||
// Synthetic DEFAULT_CODE_REVIEW_ENVIRONMENT_ID works without per-org CCR
|
||||
// setup, so no_remote_environment isn't a blocker. Server-side quota
|
||||
// consume at session creation routes billing: first N zero-rate, then
|
||||
// anthropic:cccr org-service-key (overage-only).
|
||||
if (!eligibility.eligible) {
|
||||
const blockers = eligibility.errors.filter(
|
||||
e => e.type !== 'no_remote_environment',
|
||||
)
|
||||
if (blockers.length > 0) {
|
||||
logEvent('tengu_review_remote_precondition_failed', {
|
||||
precondition_errors: blockers
|
||||
.map(e => e.type)
|
||||
.join(
|
||||
',',
|
||||
) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
})
|
||||
const reasons = blockers.map(formatPreconditionError).join('\n')
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Ultrareview cannot launch:\n${reasons}`,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedBillingNote = billingNote ?? ''
|
||||
|
||||
const prNumber = args.trim()
|
||||
const isPrNumber = /^\d+$/.test(prNumber)
|
||||
// Synthetic code_review env. Go taggedid.FromUUID(TagEnvironment,
|
||||
// UUID{...,0x02}) encodes with version prefix '01' — NOT Python's
|
||||
// legacy tagged_id() format. Verified in prod.
|
||||
const CODE_REVIEW_ENV_ID = 'env_011111111111111111111113'
|
||||
// Lite-review bypasses bughunter.go entirely, so it doesn't see the
|
||||
// webhook's bug_hunter_config (different GB project). These env vars are
|
||||
// the only tuning surface — without them, run_hunt.sh's bash defaults
|
||||
// apply (60min, 120s agent timeout), and 120s kills verifiers mid-run
|
||||
// which causes infinite respawn.
|
||||
//
|
||||
// total_wallclock must stay below RemoteAgentTask's 30min poll timeout
|
||||
// with headroom for finalization (~3min synthesis). Per-field guards
|
||||
// match autoDream.ts — GB cache can return stale wrong-type values.
|
||||
const raw = getFeatureValue_CACHED_MAY_BE_STALE<Record<
|
||||
string,
|
||||
unknown
|
||||
> | null>('tengu_review_bughunter_config', null)
|
||||
const posInt = (v: unknown, fallback: number, max?: number): number => {
|
||||
if (typeof v !== 'number' || !Number.isFinite(v)) return fallback
|
||||
const n = Math.floor(v)
|
||||
if (n <= 0) return fallback
|
||||
return max !== undefined && n > max ? fallback : n
|
||||
}
|
||||
// Upper bounds: 27min on wallclock leaves ~3min for finalization under
|
||||
// RemoteAgentTask's 30min poll timeout. If GB is set above that, the
|
||||
// hang we're fixing comes back — fall to the safe default instead.
|
||||
const commonEnvVars = {
|
||||
BUGHUNTER_DRY_RUN: '1',
|
||||
BUGHUNTER_FLEET_SIZE: String(posInt(raw?.fleet_size, 5, 20)),
|
||||
BUGHUNTER_MAX_DURATION: String(posInt(raw?.max_duration_minutes, 10, 25)),
|
||||
BUGHUNTER_AGENT_TIMEOUT: String(
|
||||
posInt(raw?.agent_timeout_seconds, 600, 1800),
|
||||
),
|
||||
BUGHUNTER_TOTAL_WALLCLOCK: String(
|
||||
posInt(raw?.total_wallclock_minutes, 22, 27),
|
||||
),
|
||||
...(process.env.BUGHUNTER_DEV_BUNDLE_B64 && {
|
||||
BUGHUNTER_DEV_BUNDLE_B64: process.env.BUGHUNTER_DEV_BUNDLE_B64,
|
||||
}),
|
||||
}
|
||||
|
||||
let session
|
||||
let command
|
||||
let target
|
||||
if (isPrNumber) {
|
||||
// PR mode: refs/pull/N/head via github.com. Orchestrator --pr N.
|
||||
const repo = await detectCurrentRepositoryWithHost()
|
||||
if (!repo || repo.host !== 'github.com') {
|
||||
logEvent('tengu_review_remote_precondition_failed', {})
|
||||
return null
|
||||
}
|
||||
session = await teleportToRemote({
|
||||
initialMessage: null,
|
||||
description: `ultrareview: ${repo.owner}/${repo.name}#${prNumber}`,
|
||||
signal: context.abortController.signal,
|
||||
branchName: `refs/pull/${prNumber}/head`,
|
||||
environmentId: CODE_REVIEW_ENV_ID,
|
||||
environmentVariables: {
|
||||
BUGHUNTER_PR_NUMBER: prNumber,
|
||||
BUGHUNTER_REPOSITORY: `${repo.owner}/${repo.name}`,
|
||||
...commonEnvVars,
|
||||
},
|
||||
})
|
||||
command = `/ultrareview ${prNumber}`
|
||||
target = `${repo.owner}/${repo.name}#${prNumber}`
|
||||
} else {
|
||||
// Branch mode: bundle the working tree, orchestrator diffs against
|
||||
// the fork point. No PR, no existing comments, no dedup.
|
||||
const baseBranch = (await getDefaultBranch()) || 'main'
|
||||
// Env-manager's `git remote remove origin` after bundle-clone
|
||||
// deletes refs/remotes/origin/* — the base branch name won't resolve
|
||||
// in the container. Pass the merge-base SHA instead: it's reachable
|
||||
// from HEAD's history so `git diff <sha>` works without a named ref.
|
||||
const { stdout: mbOut, code: mbCode } = await execFileNoThrow(
|
||||
gitExe(),
|
||||
['merge-base', baseBranch, 'HEAD'],
|
||||
{ preserveOutputOnError: false },
|
||||
)
|
||||
const mergeBaseSha = mbOut.trim()
|
||||
if (mbCode !== 0 || !mergeBaseSha) {
|
||||
logEvent('tengu_review_remote_precondition_failed', {})
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Could not find merge-base with ${baseBranch}. Make sure you're in a git repo with a ${baseBranch} branch.`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Bail early on empty diffs instead of launching a container that
|
||||
// will just echo "no changes".
|
||||
const { stdout: diffStat, code: diffCode } = await execFileNoThrow(
|
||||
gitExe(),
|
||||
['diff', '--shortstat', mergeBaseSha],
|
||||
{ preserveOutputOnError: false },
|
||||
)
|
||||
if (diffCode === 0 && !diffStat.trim()) {
|
||||
logEvent('tengu_review_remote_precondition_failed', {})
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: `No changes against the ${baseBranch} fork point. Make some commits or stage files first.`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
session = await teleportToRemote({
|
||||
initialMessage: null,
|
||||
description: `ultrareview: ${baseBranch}`,
|
||||
signal: context.abortController.signal,
|
||||
useBundle: true,
|
||||
environmentId: CODE_REVIEW_ENV_ID,
|
||||
environmentVariables: {
|
||||
BUGHUNTER_BASE_BRANCH: mergeBaseSha,
|
||||
...commonEnvVars,
|
||||
},
|
||||
})
|
||||
if (!session) {
|
||||
logEvent('tengu_review_remote_teleport_failed', {})
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Repo is too large. Push a PR and use `/ultrareview <PR#>` instead.',
|
||||
},
|
||||
]
|
||||
}
|
||||
command = '/ultrareview'
|
||||
target = baseBranch
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
logEvent('tengu_review_remote_teleport_failed', {})
|
||||
return null
|
||||
}
|
||||
registerRemoteAgentTask({
|
||||
remoteTaskType: 'ultrareview',
|
||||
session,
|
||||
command,
|
||||
context,
|
||||
isRemoteReview: true,
|
||||
})
|
||||
logEvent('tengu_review_remote_launched', {})
|
||||
const sessionUrl = getRemoteTaskSessionUrl(session.id)
|
||||
// Concise — the tool-output block is visible to the user, so the model
|
||||
// shouldn't echo the same info. Just enough for Claude to acknowledge the
|
||||
// launch without restating the target/URL (both already printed above).
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Ultrareview launched for ${target} (~10–20 min, runs in the cloud). Track: ${sessionUrl}${resolvedBillingNote} Findings arrive via task-notification. Briefly acknowledge the launch to the user without repeating the target or URL — both are already visible in the tool output above.`,
|
||||
},
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
||||
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
|
||||
|
||||
/**
|
||||
* Runtime gate for /ultrareview. GB config's `enabled` field controls
|
||||
* visibility — isEnabled() on the command filters it from getCommands()
|
||||
* when false, so ungated users don't see the command at all.
|
||||
*/
|
||||
export function isUltrareviewEnabled(): boolean {
|
||||
const cfg = getFeatureValue_CACHED_MAY_BE_STALE<Record<
|
||||
string,
|
||||
unknown
|
||||
> | null>('tengu_review_bughunter_config', null)
|
||||
return cfg?.enabled === true
|
||||
}
|
||||
Reference in New Issue
Block a user