yzcode-cli 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assistant/sessionHistory.ts +87 -0
- package/bootstrap/state.ts +1769 -0
- package/bridge/bridgeApi.ts +539 -0
- package/bridge/bridgeConfig.ts +48 -0
- package/bridge/bridgeDebug.ts +135 -0
- package/bridge/bridgeEnabled.ts +202 -0
- package/bridge/bridgeMain.ts +2999 -0
- package/bridge/bridgeMessaging.ts +461 -0
- package/bridge/bridgePermissionCallbacks.ts +43 -0
- package/bridge/bridgePointer.ts +210 -0
- package/bridge/bridgeStatusUtil.ts +163 -0
- package/bridge/bridgeUI.ts +530 -0
- package/bridge/capacityWake.ts +56 -0
- package/bridge/codeSessionApi.ts +168 -0
- package/bridge/createSession.ts +384 -0
- package/bridge/debugUtils.ts +141 -0
- package/bridge/envLessBridgeConfig.ts +165 -0
- package/bridge/flushGate.ts +71 -0
- package/bridge/inboundAttachments.ts +175 -0
- package/bridge/inboundMessages.ts +80 -0
- package/bridge/initReplBridge.ts +569 -0
- package/bridge/jwtUtils.ts +256 -0
- package/bridge/pollConfig.ts +110 -0
- package/bridge/pollConfigDefaults.ts +82 -0
- package/bridge/remoteBridgeCore.ts +1008 -0
- package/bridge/replBridge.ts +2406 -0
- package/bridge/replBridgeHandle.ts +36 -0
- package/bridge/replBridgeTransport.ts +370 -0
- package/bridge/sessionIdCompat.ts +57 -0
- package/bridge/sessionRunner.ts +550 -0
- package/bridge/trustedDevice.ts +210 -0
- package/bridge/types.ts +262 -0
- package/bridge/workSecret.ts +127 -0
- package/buddy/CompanionSprite.tsx +371 -0
- package/buddy/companion.ts +133 -0
- package/buddy/prompt.ts +36 -0
- package/buddy/sprites.ts +514 -0
- package/buddy/types.ts +148 -0
- package/buddy/useBuddyNotification.tsx +98 -0
- package/coordinator/coordinatorMode.ts +369 -0
- package/memdir/findRelevantMemories.ts +141 -0
- package/memdir/memdir.ts +507 -0
- package/memdir/memoryAge.ts +53 -0
- package/memdir/memoryScan.ts +94 -0
- package/memdir/memoryTypes.ts +271 -0
- package/memdir/paths.ts +278 -0
- package/memdir/teamMemPaths.ts +292 -0
- package/memdir/teamMemPrompts.ts +100 -0
- package/migrations/migrateAutoUpdatesToSettings.ts +61 -0
- package/migrations/migrateBypassPermissionsAcceptedToSettings.ts +40 -0
- package/migrations/migrateEnableAllProjectMcpServersToSettings.ts +118 -0
- package/migrations/migrateFennecToOpus.ts +45 -0
- package/migrations/migrateLegacyOpusToCurrent.ts +57 -0
- package/migrations/migrateOpusToOpus1m.ts +43 -0
- package/migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.ts +22 -0
- package/migrations/migrateSonnet1mToSonnet45.ts +48 -0
- package/migrations/migrateSonnet45ToSonnet46.ts +67 -0
- package/migrations/resetAutoModeOptInForDefaultOffer.ts +51 -0
- package/migrations/resetProToOpusDefault.ts +51 -0
- package/native-ts/color-diff/index.ts +999 -0
- package/native-ts/file-index/index.ts +370 -0
- package/native-ts/yoga-layout/enums.ts +134 -0
- package/native-ts/yoga-layout/index.ts +2578 -0
- package/outputStyles/loadOutputStylesDir.ts +98 -0
- package/package.json +19 -2
- package/plugins/builtinPlugins.ts +159 -0
- package/plugins/bundled/index.ts +23 -0
- package/schemas/hooks.ts +222 -0
- package/screens/Doctor.tsx +575 -0
- package/screens/REPL.tsx +5006 -0
- package/screens/ResumeConversation.tsx +399 -0
- package/server/createDirectConnectSession.ts +88 -0
- package/server/directConnectManager.ts +213 -0
- package/server/types.ts +57 -0
- package/skills/bundled/batch.ts +124 -0
- package/skills/bundled/claudeApi.ts +196 -0
- package/skills/bundled/claudeApiContent.ts +75 -0
- package/skills/bundled/claudeInChrome.ts +34 -0
- package/skills/bundled/debug.ts +103 -0
- package/skills/bundled/index.ts +79 -0
- package/skills/bundled/keybindings.ts +339 -0
- package/skills/bundled/loop.ts +92 -0
- package/skills/bundled/loremIpsum.ts +282 -0
- package/skills/bundled/remember.ts +82 -0
- package/skills/bundled/scheduleRemoteAgents.ts +447 -0
- package/skills/bundled/simplify.ts +69 -0
- package/skills/bundled/skillify.ts +197 -0
- package/skills/bundled/stuck.ts +79 -0
- package/skills/bundled/updateConfig.ts +475 -0
- package/skills/bundled/verify/SKILL.md +3 -0
- package/skills/bundled/verify/examples/cli.md +3 -0
- package/skills/bundled/verify/examples/server.md +3 -0
- package/skills/bundled/verify.ts +30 -0
- package/skills/bundled/verifyContent.ts +13 -0
- package/skills/bundledSkills.ts +220 -0
- package/skills/loadSkillsDir.ts +1086 -0
- package/skills/mcpSkillBuilders.ts +44 -0
- package/tasks/DreamTask/DreamTask.ts +157 -0
- package/tasks/InProcessTeammateTask/InProcessTeammateTask.tsx +126 -0
- package/tasks/InProcessTeammateTask/types.ts +121 -0
- package/tasks/LocalAgentTask/LocalAgentTask.tsx +683 -0
- package/tasks/LocalMainSessionTask.ts +479 -0
- package/tasks/LocalShellTask/LocalShellTask.tsx +523 -0
- package/tasks/LocalShellTask/guards.ts +41 -0
- package/tasks/LocalShellTask/killShellTasks.ts +76 -0
- package/tasks/RemoteAgentTask/RemoteAgentTask.tsx +856 -0
- package/tasks/pillLabel.ts +82 -0
- package/tasks/stopTask.ts +100 -0
- package/tasks/types.ts +46 -0
- package/upstreamproxy/relay.ts +455 -0
- package/upstreamproxy/upstreamproxy.ts +285 -0
- package/vim/motions.ts +82 -0
- package/vim/operators.ts +556 -0
- package/vim/textObjects.ts +186 -0
- package/vim/transitions.ts +490 -0
- package/vim/types.ts +199 -0
- package/voice/voiceModeEnabled.ts +54 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { logForDebugging } from '../utils/debug.js'
|
|
2
|
+
import { BridgeFatalError } from './bridgeApi.js'
|
|
3
|
+
import type { BridgeApiClient } from './types.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Ant-only fault injection for manually testing bridge recovery paths.
|
|
7
|
+
*
|
|
8
|
+
* Real failure modes this targets (BQ 2026-03-12, 7-day window):
|
|
9
|
+
* poll 404 not_found_error — 147K sessions/week, dead onEnvironmentLost gate
|
|
10
|
+
* ws_closed 1002/1006 — 22K sessions/week, zombie poll after close
|
|
11
|
+
* register transient failure — residual: network blips during doReconnect
|
|
12
|
+
*
|
|
13
|
+
* Usage: /bridge-kick <subcommand> from the REPL while Remote Control is
|
|
14
|
+
* connected, then tail debug.log to watch the recovery machinery react.
|
|
15
|
+
*
|
|
16
|
+
* Module-level state is intentional here: one bridge per REPL process, the
|
|
17
|
+
* /bridge-kick slash command has no other way to reach into initBridgeCore's
|
|
18
|
+
* closures, and teardown clears the slot.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** One-shot fault to inject on the next matching api call. */
|
|
22
|
+
type BridgeFault = {
|
|
23
|
+
method:
|
|
24
|
+
| 'pollForWork'
|
|
25
|
+
| 'registerBridgeEnvironment'
|
|
26
|
+
| 'reconnectSession'
|
|
27
|
+
| 'heartbeatWork'
|
|
28
|
+
/** Fatal errors go through handleErrorStatus → BridgeFatalError. Transient
|
|
29
|
+
* errors surface as plain axios rejections (5xx / network). Recovery code
|
|
30
|
+
* distinguishes the two: fatal → teardown, transient → retry/backoff. */
|
|
31
|
+
kind: 'fatal' | 'transient'
|
|
32
|
+
status: number
|
|
33
|
+
errorType?: string
|
|
34
|
+
/** Remaining injections. Decremented on consume; removed at 0. */
|
|
35
|
+
count: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type BridgeDebugHandle = {
|
|
39
|
+
/** Invoke the transport's permanent-close handler directly. Tests the
|
|
40
|
+
* ws_closed → reconnectEnvironmentWithSession escalation (#22148). */
|
|
41
|
+
fireClose: (code: number) => void
|
|
42
|
+
/** Call reconnectEnvironmentWithSession() — same as SIGUSR2 but
|
|
43
|
+
* reachable from the slash command. */
|
|
44
|
+
forceReconnect: () => void
|
|
45
|
+
/** Queue a fault for the next N calls to the named api method. */
|
|
46
|
+
injectFault: (fault: BridgeFault) => void
|
|
47
|
+
/** Abort the at-capacity sleep so an injected poll fault lands
|
|
48
|
+
* immediately instead of up to 10min later. */
|
|
49
|
+
wakePollLoop: () => void
|
|
50
|
+
/** env/session IDs for the debug.log grep. */
|
|
51
|
+
describe: () => string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let debugHandle: BridgeDebugHandle | null = null
|
|
55
|
+
const faultQueue: BridgeFault[] = []
|
|
56
|
+
|
|
57
|
+
export function registerBridgeDebugHandle(h: BridgeDebugHandle): void {
|
|
58
|
+
debugHandle = h
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function clearBridgeDebugHandle(): void {
|
|
62
|
+
debugHandle = null
|
|
63
|
+
faultQueue.length = 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getBridgeDebugHandle(): BridgeDebugHandle | null {
|
|
67
|
+
return debugHandle
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function injectBridgeFault(fault: BridgeFault): void {
|
|
71
|
+
faultQueue.push(fault)
|
|
72
|
+
logForDebugging(
|
|
73
|
+
`[bridge:debug] Queued fault: ${fault.method} ${fault.kind}/${fault.status}${fault.errorType ? `/${fault.errorType}` : ''} ×${fault.count}`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Wrap a BridgeApiClient so each call first checks the fault queue. If a
|
|
79
|
+
* matching fault is queued, throw the specified error instead of calling
|
|
80
|
+
* through. Delegates everything else to the real client.
|
|
81
|
+
*
|
|
82
|
+
* Only called when USER_TYPE === 'ant' — zero overhead in external builds.
|
|
83
|
+
*/
|
|
84
|
+
export function wrapApiForFaultInjection(
|
|
85
|
+
api: BridgeApiClient,
|
|
86
|
+
): BridgeApiClient {
|
|
87
|
+
function consume(method: BridgeFault['method']): BridgeFault | null {
|
|
88
|
+
const idx = faultQueue.findIndex(f => f.method === method)
|
|
89
|
+
if (idx === -1) return null
|
|
90
|
+
const fault = faultQueue[idx]!
|
|
91
|
+
fault.count--
|
|
92
|
+
if (fault.count <= 0) faultQueue.splice(idx, 1)
|
|
93
|
+
return fault
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function throwFault(fault: BridgeFault, context: string): never {
|
|
97
|
+
logForDebugging(
|
|
98
|
+
`[bridge:debug] Injecting ${fault.kind} fault into ${context}: status=${fault.status} errorType=${fault.errorType ?? 'none'}`,
|
|
99
|
+
)
|
|
100
|
+
if (fault.kind === 'fatal') {
|
|
101
|
+
throw new BridgeFatalError(
|
|
102
|
+
`[injected] ${context} ${fault.status}`,
|
|
103
|
+
fault.status,
|
|
104
|
+
fault.errorType,
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
// Transient: mimic an axios rejection (5xx / network). No .status on
|
|
108
|
+
// the error itself — that's how the catch blocks distinguish.
|
|
109
|
+
throw new Error(`[injected transient] ${context} ${fault.status}`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...api,
|
|
114
|
+
async pollForWork(envId, secret, signal, reclaimMs) {
|
|
115
|
+
const f = consume('pollForWork')
|
|
116
|
+
if (f) throwFault(f, 'Poll')
|
|
117
|
+
return api.pollForWork(envId, secret, signal, reclaimMs)
|
|
118
|
+
},
|
|
119
|
+
async registerBridgeEnvironment(config) {
|
|
120
|
+
const f = consume('registerBridgeEnvironment')
|
|
121
|
+
if (f) throwFault(f, 'Registration')
|
|
122
|
+
return api.registerBridgeEnvironment(config)
|
|
123
|
+
},
|
|
124
|
+
async reconnectSession(envId, sessionId) {
|
|
125
|
+
const f = consume('reconnectSession')
|
|
126
|
+
if (f) throwFault(f, 'ReconnectSession')
|
|
127
|
+
return api.reconnectSession(envId, sessionId)
|
|
128
|
+
},
|
|
129
|
+
async heartbeatWork(envId, workId, token) {
|
|
130
|
+
const f = consume('heartbeatWork')
|
|
131
|
+
if (f) throwFault(f, 'Heartbeat')
|
|
132
|
+
return api.heartbeatWork(envId, workId, token)
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { feature } from 'bun:bundle'
|
|
2
|
+
import {
|
|
3
|
+
checkGate_CACHED_OR_BLOCKING,
|
|
4
|
+
getDynamicConfig_CACHED_MAY_BE_STALE,
|
|
5
|
+
getFeatureValue_CACHED_MAY_BE_STALE,
|
|
6
|
+
} from '../services/analytics/growthbook.js'
|
|
7
|
+
// Namespace import breaks the bridgeEnabled → auth → config → bridgeEnabled
|
|
8
|
+
// cycle — authModule.foo is a live binding, so by the time the helpers below
|
|
9
|
+
// call it, auth.js is fully loaded. Previously used require() for the same
|
|
10
|
+
// deferral, but require() hits a CJS cache that diverges from the ESM
|
|
11
|
+
// namespace after mock.module() (daemon/auth.test.ts), breaking spyOn.
|
|
12
|
+
import * as authModule from '../utils/auth.js'
|
|
13
|
+
import { isEnvTruthy } from '../utils/envUtils.js'
|
|
14
|
+
import { lt } from '../utils/semver.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runtime check for bridge mode entitlement.
|
|
18
|
+
*
|
|
19
|
+
* Remote Control requires a claude.ai subscription (the bridge auths to CCR
|
|
20
|
+
* with the claude.ai OAuth token). isClaudeAISubscriber() excludes
|
|
21
|
+
* Bedrock/Vertex/Foundry, apiKeyHelper/gateway deployments, env-var API keys,
|
|
22
|
+
* and Console API logins — none of which have the OAuth token CCR needs.
|
|
23
|
+
* See github.com/deshaw/anthropic-issues/issues/24.
|
|
24
|
+
*
|
|
25
|
+
* The `feature('BRIDGE_MODE')` guard ensures the GrowthBook string literal
|
|
26
|
+
* is only referenced when bridge mode is enabled at build time.
|
|
27
|
+
*/
|
|
28
|
+
export function isBridgeEnabled(): boolean {
|
|
29
|
+
// Positive ternary pattern — see docs/feature-gating.md.
|
|
30
|
+
// Negative pattern (if (!feature(...)) return) does not eliminate
|
|
31
|
+
// inline string literals from external builds.
|
|
32
|
+
return feature('BRIDGE_MODE')
|
|
33
|
+
? isClaudeAISubscriber() &&
|
|
34
|
+
getFeatureValue_CACHED_MAY_BE_STALE('tengu_ccr_bridge', false)
|
|
35
|
+
: false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Blocking entitlement check for Remote Control.
|
|
40
|
+
*
|
|
41
|
+
* Returns cached `true` immediately (fast path). If the disk cache says
|
|
42
|
+
* `false` or is missing, awaits GrowthBook init and fetches the fresh
|
|
43
|
+
* server value (slow path, max ~5s), then writes it to disk.
|
|
44
|
+
*
|
|
45
|
+
* Use at entitlement gates where a stale `false` would unfairly block access.
|
|
46
|
+
* For user-facing error paths, prefer `getBridgeDisabledReason()` which gives
|
|
47
|
+
* a specific diagnostic. For render-body UI visibility checks, use
|
|
48
|
+
* `isBridgeEnabled()` instead.
|
|
49
|
+
*/
|
|
50
|
+
export async function isBridgeEnabledBlocking(): Promise<boolean> {
|
|
51
|
+
return feature('BRIDGE_MODE')
|
|
52
|
+
? isClaudeAISubscriber() &&
|
|
53
|
+
(await checkGate_CACHED_OR_BLOCKING('tengu_ccr_bridge'))
|
|
54
|
+
: false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Diagnostic message for why Remote Control is unavailable, or null if
|
|
59
|
+
* it's enabled. Call this instead of a bare `isBridgeEnabledBlocking()`
|
|
60
|
+
* check when you need to show the user an actionable error.
|
|
61
|
+
*
|
|
62
|
+
* The GrowthBook gate targets on organizationUUID, which comes from
|
|
63
|
+
* config.oauthAccount — populated by /api/oauth/profile during login.
|
|
64
|
+
* That endpoint requires the user:profile scope. Tokens without it
|
|
65
|
+
* (setup-token, CLAUDE_CODE_OAUTH_TOKEN env var, or pre-scope-expansion
|
|
66
|
+
* logins) leave oauthAccount unpopulated, so the gate falls back to
|
|
67
|
+
* false and users see a dead-end "not enabled" message with no hint
|
|
68
|
+
* that re-login would fix it. See CC-1165 / gh-33105.
|
|
69
|
+
*/
|
|
70
|
+
export async function getBridgeDisabledReason(): Promise<string | null> {
|
|
71
|
+
if (feature('BRIDGE_MODE')) {
|
|
72
|
+
if (!isClaudeAISubscriber()) {
|
|
73
|
+
return 'Remote Control requires a claude.ai subscription. Run `claude auth login` to sign in with your claude.ai account.'
|
|
74
|
+
}
|
|
75
|
+
if (!hasProfileScope()) {
|
|
76
|
+
return 'Remote Control requires a full-scope login token. Long-lived tokens (from `claude setup-token` or CLAUDE_CODE_OAUTH_TOKEN) are limited to inference-only for security reasons. Run `claude auth login` to use Remote Control.'
|
|
77
|
+
}
|
|
78
|
+
if (!getOauthAccountInfo()?.organizationUuid) {
|
|
79
|
+
return 'Unable to determine your organization for Remote Control eligibility. Run `claude auth login` to refresh your account information.'
|
|
80
|
+
}
|
|
81
|
+
if (!(await checkGate_CACHED_OR_BLOCKING('tengu_ccr_bridge'))) {
|
|
82
|
+
return 'Remote Control is not yet enabled for your account.'
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
return 'Remote Control is not available in this build.'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// try/catch: main.tsx:5698 calls isBridgeEnabled() while defining the Commander
|
|
90
|
+
// program, before enableConfigs() runs. isClaudeAISubscriber() → getGlobalConfig()
|
|
91
|
+
// throws "Config accessed before allowed" there. Pre-config, no OAuth token can
|
|
92
|
+
// exist anyway — false is correct. Same swallow getFeatureValue_CACHED_MAY_BE_STALE
|
|
93
|
+
// already does at growthbook.ts:775-780.
|
|
94
|
+
function isClaudeAISubscriber(): boolean {
|
|
95
|
+
try {
|
|
96
|
+
return authModule.isClaudeAISubscriber()
|
|
97
|
+
} catch {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function hasProfileScope(): boolean {
|
|
102
|
+
try {
|
|
103
|
+
return authModule.hasProfileScope()
|
|
104
|
+
} catch {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function getOauthAccountInfo(): ReturnType<
|
|
109
|
+
typeof authModule.getOauthAccountInfo
|
|
110
|
+
> {
|
|
111
|
+
try {
|
|
112
|
+
return authModule.getOauthAccountInfo()
|
|
113
|
+
} catch {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Runtime check for the env-less (v2) REPL bridge path.
|
|
120
|
+
* Returns true when the GrowthBook flag `tengu_bridge_repl_v2` is enabled.
|
|
121
|
+
*
|
|
122
|
+
* This gates which implementation initReplBridge uses — NOT whether bridge
|
|
123
|
+
* is available at all (see isBridgeEnabled above). Daemon/print paths stay
|
|
124
|
+
* on the env-based implementation regardless of this gate.
|
|
125
|
+
*/
|
|
126
|
+
export function isEnvLessBridgeEnabled(): boolean {
|
|
127
|
+
return feature('BRIDGE_MODE')
|
|
128
|
+
? getFeatureValue_CACHED_MAY_BE_STALE('tengu_bridge_repl_v2', false)
|
|
129
|
+
: false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Kill-switch for the `cse_*` → `session_*` client-side retag shim.
|
|
134
|
+
*
|
|
135
|
+
* The shim exists because compat/convert.go:27 validates TagSession and the
|
|
136
|
+
* claude.ai frontend routes on `session_*`, while v2 worker endpoints hand out
|
|
137
|
+
* `cse_*`. Once the server tags by environment_kind and the frontend accepts
|
|
138
|
+
* `cse_*` directly, flip this to false to make toCompatSessionId a no-op.
|
|
139
|
+
* Defaults to true — the shim stays active until explicitly disabled.
|
|
140
|
+
*/
|
|
141
|
+
export function isCseShimEnabled(): boolean {
|
|
142
|
+
return feature('BRIDGE_MODE')
|
|
143
|
+
? getFeatureValue_CACHED_MAY_BE_STALE(
|
|
144
|
+
'tengu_bridge_repl_v2_cse_shim_enabled',
|
|
145
|
+
true,
|
|
146
|
+
)
|
|
147
|
+
: true
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Returns an error message if the current CLI version is below the
|
|
152
|
+
* minimum required for the v1 (env-based) Remote Control path, or null if the
|
|
153
|
+
* version is fine. The v2 (env-less) path uses checkEnvLessBridgeMinVersion()
|
|
154
|
+
* in envLessBridgeConfig.ts instead — the two implementations have independent
|
|
155
|
+
* version floors.
|
|
156
|
+
*
|
|
157
|
+
* Uses cached (non-blocking) GrowthBook config. If GrowthBook hasn't
|
|
158
|
+
* loaded yet, the default '0.0.0' means the check passes — a safe fallback.
|
|
159
|
+
*/
|
|
160
|
+
export function checkBridgeMinVersion(): string | null {
|
|
161
|
+
// Positive pattern — see docs/feature-gating.md.
|
|
162
|
+
// Negative pattern (if (!feature(...)) return) does not eliminate
|
|
163
|
+
// inline string literals from external builds.
|
|
164
|
+
if (feature('BRIDGE_MODE')) {
|
|
165
|
+
const config = getDynamicConfig_CACHED_MAY_BE_STALE<{
|
|
166
|
+
minVersion: string
|
|
167
|
+
}>('tengu_bridge_min_version', { minVersion: '0.0.0' })
|
|
168
|
+
if (config.minVersion && lt(MACRO.VERSION, config.minVersion)) {
|
|
169
|
+
return `Your version of Claude Code (${MACRO.VERSION}) is too old for Remote Control.\nVersion ${config.minVersion} or higher is required. Run \`claude update\` to update.`
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Default for remoteControlAtStartup when the user hasn't explicitly set it.
|
|
177
|
+
* When the CCR_AUTO_CONNECT build flag is present (ant-only) and the
|
|
178
|
+
* tengu_cobalt_harbor GrowthBook gate is on, all sessions connect to CCR by
|
|
179
|
+
* default — the user can still opt out by setting remoteControlAtStartup=false
|
|
180
|
+
* in config (explicit settings always win over this default).
|
|
181
|
+
*
|
|
182
|
+
* Defined here rather than in config.ts to avoid a direct
|
|
183
|
+
* config.ts → growthbook.ts import cycle (growthbook.ts → user.ts → config.ts).
|
|
184
|
+
*/
|
|
185
|
+
export function getCcrAutoConnectDefault(): boolean {
|
|
186
|
+
return feature('CCR_AUTO_CONNECT')
|
|
187
|
+
? getFeatureValue_CACHED_MAY_BE_STALE('tengu_cobalt_harbor', false)
|
|
188
|
+
: false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Opt-in CCR mirror mode — every local session spawns an outbound-only
|
|
193
|
+
* Remote Control session that receives forwarded events. Separate from
|
|
194
|
+
* getCcrAutoConnectDefault (bidirectional Remote Control). Env var wins for
|
|
195
|
+
* local opt-in; GrowthBook controls rollout.
|
|
196
|
+
*/
|
|
197
|
+
export function isCcrMirrorEnabled(): boolean {
|
|
198
|
+
return feature('CCR_MIRROR')
|
|
199
|
+
? isEnvTruthy(process.env.CLAUDE_CODE_CCR_MIRROR) ||
|
|
200
|
+
getFeatureValue_CACHED_MAY_BE_STALE('tengu_ccr_mirror', false)
|
|
201
|
+
: false
|
|
202
|
+
}
|