refactor kiro flow to web signin authorization

This commit is contained in:
QLHazyCoder
2026-05-19 12:16:59 +08:00
parent 98e776348e
commit ef700be3e2
22 changed files with 521 additions and 1391 deletions
@@ -785,6 +785,10 @@
if (!cleanString(runtimeState.register?.email || currentState?.email)) {
throw new Error('缺少已注册邮箱,请先完成注册页步骤。');
}
if (cleanString(runtimeState.register?.status) !== 'completed'
|| cleanString(runtimeState.webAuth?.status) !== 'signed_in') {
throw new Error('Kiro Web 登录态尚未建立,请先完成步骤 6。');
}
const client = await desktopClientApi.registerDesktopClient({
region: DEFAULT_REGION,
+117 -124
View File
@@ -6,16 +6,11 @@
const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || 'us-east-1';
const DEFAULT_TARGET_ID = kiroStateApi?.DEFAULT_TARGET_ID || 'kiro-rs';
const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = kiroTimeoutApi?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000);
const DEVICE_LOGIN_START_URL = 'https://view.awsapps.com/start';
const DEFAULT_SCOPES = Object.freeze([
'codewhisperer:completions',
'codewhisperer:analysis',
'codewhisperer:conversations',
'codewhisperer:transformations',
'codewhisperer:taskassist',
]);
const KIRO_SIGNIN_URL = 'https://app.kiro.dev/signin';
const KIRO_REGISTER_PAGE_SOURCE_ID = 'kiro-register-page';
const KIRO_STEP1_COOKIE_CLEAR_DOMAINS = Object.freeze([
'kiro.dev',
'app.kiro.dev',
'awsapps.com',
'view.awsapps.com',
'login.awsapps.com',
@@ -26,6 +21,8 @@
'profile.aws.amazon.com',
]);
const KIRO_STEP1_COOKIE_CLEAR_ORIGINS = Object.freeze([
'https://app.kiro.dev',
'https://kiro.dev',
'https://view.awsapps.com',
'https://login.awsapps.com',
'https://oidc.us-east-1.amazonaws.com',
@@ -110,10 +107,6 @@
return String(value ?? '').trim();
}
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
return cleanString(value) || fallback;
}
function normalizePositiveInteger(value, fallback) {
const numeric = Math.floor(Number(value));
if (Number.isInteger(numeric) && numeric > 0) {
@@ -152,10 +145,6 @@
);
}
function buildOidcBaseUrl(region = DEFAULT_REGION) {
return `https://oidc.${normalizeRegion(region)}.amazonaws.com`;
}
function readKiroRuntime(state = {}) {
if (typeof kiroStateApi?.ensureRuntimeState === 'function') {
return kiroStateApi.ensureRuntimeState(state);
@@ -178,17 +167,6 @@
return error instanceof Error ? error.message : String(error ?? '未知错误');
}
async function readResponse(response) {
const text = await response.text();
let json = null;
try {
json = text ? JSON.parse(text) : null;
} catch (_error) {
json = null;
}
return { text, json };
}
function normalizeKiroCookieDomain(domain = '') {
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
}
@@ -260,6 +238,58 @@
return cookies;
}
function shouldReadKiroWebCookie(cookie) {
const domain = normalizeKiroCookieDomain(cookie?.domain);
return domain === 'kiro.dev'
|| domain === 'app.kiro.dev'
|| domain.endsWith('.app.kiro.dev');
}
async function collectKiroWebCookies(chromeApi) {
if (!chromeApi?.cookies?.getAll) {
return [];
}
const stores = chromeApi.cookies.getAllCookieStores
? await chromeApi.cookies.getAllCookieStores()
: [{ id: undefined }];
const cookies = [];
const seen = new Set();
for (const store of stores) {
const storeId = store?.id;
const batch = await chromeApi.cookies.getAll(storeId ? { storeId } : {});
for (const cookie of batch || []) {
if (!shouldReadKiroWebCookie(cookie)) {
continue;
}
const key = [
cookie.storeId || storeId || '',
cookie.domain || '',
cookie.path || '',
cookie.name || '',
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
].join('|');
if (seen.has(key)) {
continue;
}
seen.add(key);
cookies.push(cookie);
}
}
return cookies;
}
async function captureKiroWebAuthSummary() {
const cookies = await collectKiroWebCookies(chrome);
const names = new Set(cookies.map((cookie) => cleanString(cookie?.name).toLowerCase()).filter(Boolean));
return {
hasAccessToken: names.has('accesstoken') || names.has('access_token'),
hasSessionToken: names.has('sessiontoken') || names.has('session_token'),
};
}
async function removeKiroStep1Cookie(chromeApi, cookie) {
const details = {
url: buildKiroStep1CookieRemovalUrl(cookie),
@@ -285,75 +315,6 @@
}
}
async function startBuilderIdDeviceLogin(region, fetchImpl) {
const normalizedRegion = normalizeRegion(region);
const oidcBaseUrl = buildOidcBaseUrl(normalizedRegion);
const registerResponse = await fetchImpl(`${oidcBaseUrl}/client/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientName: 'Codex Registration Extension',
clientType: 'public',
scopes: DEFAULT_SCOPES,
grantTypes: ['urn:ietf:params:oauth:grant-type:device_code', 'refresh_token'],
issuerUrl: DEVICE_LOGIN_START_URL,
}),
});
const registerBody = await readResponse(registerResponse);
if (!registerResponse.ok) {
throw new Error(`Builder ID 客户端注册失败:${cleanString(registerBody.text || registerResponse.statusText) || registerResponse.status}`);
}
const clientId = cleanString(registerBody.json?.clientId);
const clientSecret = String(registerBody.json?.clientSecret || '');
if (!clientId || !clientSecret) {
throw new Error('Builder ID 客户端注册响应缺少凭据。');
}
const authorizationResponse = await fetchImpl(`${oidcBaseUrl}/device_authorization`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId,
clientSecret,
startUrl: DEVICE_LOGIN_START_URL,
}),
});
const authorizationBody = await readResponse(authorizationResponse);
if (!authorizationResponse.ok) {
throw new Error(`Builder ID 设备授权失败:${cleanString(authorizationBody.text || authorizationResponse.statusText) || authorizationResponse.status}`);
}
const deviceCode = String(authorizationBody.json?.deviceCode || '');
const userCode = cleanString(authorizationBody.json?.userCode);
const verificationUri = cleanString(authorizationBody.json?.verificationUri);
const verificationUriComplete = cleanString(
authorizationBody.json?.verificationUriComplete || verificationUri
);
const interval = normalizePositiveInteger(authorizationBody.json?.interval, 5);
const expiresIn = normalizePositiveInteger(authorizationBody.json?.expiresIn, 600);
if (!deviceCode || !userCode || !verificationUriComplete) {
throw new Error('Builder ID 设备授权响应缺少必要字段。');
}
return {
clientId,
clientSecret,
deviceCode,
expiresAt: Date.now() + expiresIn * 1000,
expiresIn,
interval,
region: normalizedRegion,
userCode,
verificationUri,
verificationUriComplete,
};
}
function createKiroRegisterRunner(deps = {}) {
const {
addLog = async () => {},
@@ -362,7 +323,6 @@
ensureContentScriptReadyOnTab = null,
ensureIcloudMailSession = null,
ensureMail2925MailboxSession = null,
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
generatePassword = null,
generateRandomName = null,
getMailConfig = null,
@@ -399,9 +359,6 @@
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Kiro register runner requires completeNodeFromBackground.');
}
if (typeof fetchImpl !== 'function') {
throw new Error('Kiro register runner requires fetch support.');
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
@@ -883,24 +840,51 @@
const currentState = await getExecutionState(state);
try {
await clearKiroCookiesBeforeStep1();
const auth = await startBuilderIdDeviceLogin(DEFAULT_REGION, fetchImpl);
const loginUrl = cleanString(auth.verificationUriComplete || auth.verificationUri);
const tabId = loginUrl ? await reuseOrCreateTab(KIRO_REGISTER_PAGE_SOURCE_ID, loginUrl) : null;
const loginUrl = KIRO_SIGNIN_URL;
const tabId = await reuseOrCreateTab(KIRO_REGISTER_PAGE_SOURCE_ID, loginUrl);
if (!Number.isInteger(tabId)) {
throw new Error('无法打开 Kiro 注册页,请重试步骤 1。');
}
await registerTab(KIRO_REGISTER_PAGE_SOURCE_ID, tabId);
await activateTab(tabId);
const landingResult = await ensureKiroPageState(tabId, {
let landingResult = await ensureKiroPageState(tabId, {
step: 1,
targetStates: ['email_entry'],
targetStates: ['kiro_signin_page', 'email_entry'],
stableMs: 2500,
initialDelayMs: 300,
injectLogMessage: '步骤 1:Kiro 注册页内容脚本未就绪,正在等待页面恢复...',
readyLogMessage: '步骤 1:正在等待 Kiro 注册页邮箱输入框加载完成...',
readyLogMessage: '步骤 1:正在等待 Kiro 官方登录页加载完成...',
});
if (landingResult?.state === 'kiro_signin_page') {
await log('步骤 1:正在选择 AWS Builder ID 登录方式...', 'info', nodeId);
const selectResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, {
type: 'EXECUTE_NODE',
nodeId: 'kiro-open-register-page',
step: 1,
source: 'background',
payload: {},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
onRetryableError: buildKiroRetryRecovery(tabId, {}),
logMessage: '步骤 1:正在点击 Kiro 官方登录页的 Builder ID...',
});
if (selectResult?.error) {
throw new Error(selectResult.error);
}
landingResult = await ensureKiroPageState(tabId, {
step: 1,
targetStates: ['email_entry'],
stableMs: 2500,
initialDelayMs: 300,
injectLogMessage: '步骤 1:选择 Builder ID 后页面跳转中,正在等待 Kiro 注册页恢复...',
readyLogMessage: '步骤 1:正在等待 Builder ID 邮箱输入框加载完成...',
timeoutMessage: '选择 Builder ID 后未进入邮箱页,请检查当前 Kiro 登录页或代理状态。',
});
}
const nextPatch = {
session: {
currentStage: 'register',
@@ -915,18 +899,16 @@
email: '',
fullName: '',
verificationRequestedAt: 0,
entryClientId: auth.clientId,
entryClientSecret: auth.clientSecret,
entryDeviceCode: auth.deviceCode,
userCode: auth.userCode,
loginUrl,
verificationUri: auth.verificationUri,
verificationUriComplete: loginUrl,
entryExpiresAt: auth.expiresAt,
entryIntervalSeconds: auth.interval,
status: 'waiting_email',
completedAt: 0,
},
webAuth: {
status: 'signin_started',
completedAt: 0,
hasAccessToken: false,
hasSessionToken: false,
},
desktopAuth: {
region: DEFAULT_REGION,
clientId: '',
@@ -957,7 +939,7 @@
};
const payload = await applyRuntimeState(currentState, nextPatch);
await log(`Kiro 注册页已就绪,请在下一步中获取邮箱并继续。当前授权码:${auth.userCode}`, 'ok', nodeId);
await log('Kiro 注册页已就绪,已进入 Builder ID 邮箱页,请在下一步中获取邮箱并继续。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
} catch (error) {
const message = getErrorMessage(error);
@@ -1315,7 +1297,7 @@
});
let landingResult = await ensureKiroPageState(tabId, {
step: 6,
targetStates: ['authorization_page', 'success_page'],
targetStates: ['authorization_page', 'kiro_web_signed_in'],
stableMs: 1500,
initialDelayMs: 150,
injectLogMessage: '步骤 6:Kiro 授权确认页内容脚本未就绪,正在等待页面恢复...',
@@ -1323,7 +1305,7 @@
timeoutMessage: '未进入 Kiro 授权确认页,请检查当前页面状态。',
});
if (landingResult?.state !== 'success_page') {
if (landingResult?.state !== 'kiro_web_signed_in') {
await log('步骤 6:正在确认访问并完成 Kiro 注册授权...', 'info', nodeId);
const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, {
type: 'EXECUTE_NODE',
@@ -1342,12 +1324,18 @@
if (submitResult?.error) {
throw new Error(submitResult.error);
}
landingResult = {
state: String(submitResult?.state || ''),
url: String(submitResult?.url || ''),
};
landingResult = await ensureKiroPageState(tabId, {
step: 6,
targetStates: ['kiro_web_signed_in'],
stableMs: 2000,
initialDelayMs: 300,
injectLogMessage: '步骤 6:授权确认后页面跳转中,正在等待 Kiro Web 登录态恢复...',
readyLogMessage: '步骤 6:授权确认已提交,正在等待回到 Kiro Web...',
timeoutMessage: '授权确认后未回到 Kiro Web 登录完成页,请检查当前页面或代理状态。',
});
}
const webAuthSummary = await captureKiroWebAuthSummary();
const payload = await applyRuntimeState(currentState, {
session: {
currentStage: 'desktop-authorize',
@@ -1359,12 +1347,18 @@
status: 'completed',
completedAt: Date.now(),
},
webAuth: {
status: 'signed_in',
completedAt: Date.now(),
hasAccessToken: Boolean(webAuthSummary.hasAccessToken),
hasSessionToken: Boolean(webAuthSummary.hasSessionToken),
},
upload: {
status: 'waiting_desktop_authorize',
error: '',
},
});
await log('步骤 6:注册页授权已完成,Builder ID 浏览器会话已建立。', 'ok', nodeId);
await log('步骤 6:注册页授权已完成,Kiro Web 登录态已建立。', 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
} catch (error) {
const message = getErrorMessage(error);
@@ -1380,12 +1374,11 @@
executeKiroSubmitName,
executeKiroSubmitPassword,
executeKiroSubmitVerificationCode,
startBuilderIdDeviceLogin,
};
}
return {
createKiroRegisterRunner,
startBuilderIdDeviceLogin,
KIRO_SIGNIN_URL,
};
});
+19 -16
View File
@@ -58,6 +58,13 @@
return Number.isInteger(numeric) ? numeric : fallback;
}
function normalizeBoolean(value, fallback = false) {
if (value === true || value === false) {
return value;
}
return fallback;
}
function buildDefaultRuntimeState() {
return {
session: {
@@ -74,18 +81,16 @@
email: '',
fullName: '',
verificationRequestedAt: 0,
entryClientId: '',
entryClientSecret: '',
entryDeviceCode: '',
userCode: '',
loginUrl: '',
verificationUri: '',
verificationUriComplete: '',
entryExpiresAt: 0,
entryIntervalSeconds: 0,
status: '',
completedAt: 0,
},
webAuth: {
status: '',
completedAt: 0,
hasAccessToken: false,
hasSessionToken: false,
},
desktopAuth: {
region: DEFAULT_REGION,
clientId: '',
@@ -133,18 +138,16 @@
email: normalizeString(merged.register?.email),
fullName: normalizeString(merged.register?.fullName),
verificationRequestedAt: Math.max(0, normalizeInteger(merged.register?.verificationRequestedAt)),
entryClientId: normalizeString(merged.register?.entryClientId),
entryClientSecret: normalizeString(merged.register?.entryClientSecret),
entryDeviceCode: normalizeString(merged.register?.entryDeviceCode),
userCode: normalizeString(merged.register?.userCode),
loginUrl: normalizeString(merged.register?.loginUrl),
verificationUri: normalizeString(merged.register?.verificationUri),
verificationUriComplete: normalizeString(merged.register?.verificationUriComplete),
entryExpiresAt: Math.max(0, normalizeInteger(merged.register?.entryExpiresAt)),
entryIntervalSeconds: Math.max(0, normalizeInteger(merged.register?.entryIntervalSeconds)),
status: normalizeString(merged.register?.status),
completedAt: Math.max(0, normalizeInteger(merged.register?.completedAt)),
},
webAuth: {
status: normalizeString(merged.webAuth?.status),
completedAt: Math.max(0, normalizeInteger(merged.webAuth?.completedAt)),
hasAccessToken: normalizeBoolean(merged.webAuth?.hasAccessToken),
hasSessionToken: normalizeBoolean(merged.webAuth?.hasSessionToken),
},
desktopAuth: {
region: normalizeString(merged.desktopAuth?.region, DEFAULT_REGION),
clientId: normalizeString(merged.desktopAuth?.clientId),
+76 -3
View File
@@ -3,6 +3,7 @@ console.log('[MultiPage:kiro-register-page] Content script loaded on', location.
const KIRO_REGISTER_PAGE_LISTENER_SENTINEL = 'data-multipage-kiro-register-page-listener';
const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = globalThis.MultiPageKiroTimeouts?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000);
const KIRO_CONTINUE_TEXT_PATTERN = /continue|继续/i;
const KIRO_BUILDER_ID_TEXT_PATTERN = /aws\s*builder\s*id|builder\s*id/i;
const KIRO_CONFIRM_CONTINUE_TEXT_PATTERN = /confirm and continue|确认并继续/i;
const KIRO_ALLOW_ACCESS_TEXT_PATTERN = /allow access|允许访问/i;
const KIRO_SUCCESS_TEXT_PATTERN = /authorization successful|you may now close this window|you are now signed in|授权成功|可以关闭此窗口|已登录/i;
@@ -174,6 +175,39 @@ function findPasswordContinueButton(passwordInput = null) {
});
}
function isKiroWebHost(hostname = '') {
const normalized = String(hostname || '').trim().toLowerCase();
return normalized === 'app.kiro.dev'
|| normalized === 'kiro.dev';
}
function parseCurrentUrl() {
try {
return new URL(location.href);
} catch (_error) {
return null;
}
}
function findBuilderIdButton() {
return findActionButton({
textPattern: KIRO_BUILDER_ID_TEXT_PATTERN,
});
}
function isKiroWebSignedInPage(pageText = '') {
const parsedUrl = parseCurrentUrl();
const pathname = String(parsedUrl?.pathname || '').replace(/\/+$/, '') || '/';
const authStatus = String(parsedUrl?.searchParams?.get('auth_status') || '').trim().toLowerCase();
if (authStatus === 'success') {
return true;
}
if (pathname !== '/signin') {
return true;
}
return /signed\s*in|authorization\s*successful|登录成功|已登录|授权成功/i.test(pageText);
}
function getAuthorizationActionKind(text = '') {
if (KIRO_CONFIRM_CONTINUE_TEXT_PATTERN.test(text)) {
return 'confirm_continue';
@@ -264,6 +298,24 @@ function detectKiroRegisterPageState() {
return fatalState;
}
if (isKiroWebHost(location.hostname || '')) {
const builderIdButton = findBuilderIdButton();
if (builderIdButton) {
return {
state: 'kiro_signin_page',
url: currentUrl,
actionButton: builderIdButton,
actionText: getElementActionText(builderIdButton),
};
}
if (isKiroWebSignedInPage(pageText)) {
return {
state: 'kiro_web_signed_in',
url: currentUrl,
};
}
}
const passwordInputs = findVisiblePasswordInputs();
const confirmPasswordInput = findVisibleConfirmPasswordInput();
if (passwordInputs.length) {
@@ -398,7 +450,7 @@ async function waitForKiroRegisterStateChange(payload = {}) {
async function waitForKiroAuthorizationAdvance(previousState = {}, options = {}) {
return waitForKiroState(
(detected) => {
if (detected.state === 'success_page') {
if (detected.state === 'success_page' || detected.state === 'kiro_web_signed_in') {
return true;
}
if (detected.state !== 'authorization_page') {
@@ -421,6 +473,24 @@ async function waitForKiroAuthorizationAdvance(previousState = {}, options = {})
);
}
async function selectKiroBuilderId() {
const readyState = await ensureKiroRegisterPageState({
targetStates: ['kiro_signin_page'],
timeoutMs: DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: 250,
});
if (!readyState.actionButton) {
throw new Error('Kiro 官方登录页未找到 Builder ID 登录按钮。');
}
simulateClick(readyState.actionButton);
return {
submitted: true,
state: 'builder_id_selected',
url: location.href,
actionText: readyState.actionText || '',
};
}
async function submitKiroEmail(payload = {}) {
const email = String(payload?.email || '').trim();
if (!email) {
@@ -534,7 +604,7 @@ async function submitKiroPassword(payload = {}) {
async function confirmKiroRegisterConsent(payload = {}) {
let currentState = await ensureKiroRegisterPageState({
targetStates: ['authorization_page', 'success_page'],
targetStates: ['authorization_page', 'success_page', 'kiro_web_signed_in'],
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
retryDelayMs: payload?.retryDelayMs || 250,
});
@@ -558,7 +628,7 @@ async function confirmKiroRegisterConsent(payload = {}) {
});
}
if (currentState.state !== 'success_page') {
if (currentState.state !== 'success_page' && currentState.state !== 'kiro_web_signed_in') {
throw new Error('Kiro 授权页未完成确认访问流程。');
}
@@ -578,6 +648,9 @@ async function handleKiroRegisterCommand(message) {
return waitForKiroRegisterStateChange(message.payload || {});
case 'EXECUTE_NODE': {
const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim();
if (nodeId === 'kiro-open-register-page') {
return selectKiroBuilderId();
}
if (nodeId === 'kiro-submit-email') {
return submitKiroEmail(message.payload || {});
}
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -255,6 +255,7 @@
'content/kiro/register-page': {
sourceId: 'kiro-register-page',
commands: [
'kiro-open-register-page',
'kiro-submit-email',
'kiro-submit-name',
'kiro-submit-verification-code',
@@ -362,7 +363,7 @@
'kiro-runtime-status': {
id: 'kiro-runtime-status',
label: 'Kiro 运行态',
rowIds: ['row-kiro-device-code', 'row-kiro-login-url', 'row-kiro-upload-status'],
rowIds: ['row-kiro-web-status', 'row-kiro-login-url', 'row-kiro-upload-status'],
},
});
+14 -3
View File
@@ -143,7 +143,13 @@
|| normalizedHost.includes(`.${normalizedFamily}.`);
}
function isKiroAuthHost(hostname = '') {
function isKiroWebHost(hostname = '') {
const normalized = normalizeHostname(hostname);
return normalized === 'app.kiro.dev'
|| normalized === 'kiro.dev';
}
function isKiroAwsAuthHost(hostname = '') {
const normalized = normalizeHostname(hostname);
return normalized === 'view.awsapps.com'
|| normalized === 'login.awsapps.com'
@@ -153,6 +159,10 @@
|| normalized.endsWith('.amazonaws.com');
}
function isKiroRegisterHost(hostname = '') {
return isKiroWebHost(hostname) || isKiroAwsAuthHost(hostname);
}
function getRuntimeSourceDefinitions() {
return {
...(typeof flowRegistryApi.getRuntimeSourceDefinitions === 'function'
@@ -327,8 +337,9 @@
case 'gopay-flow':
return /gopay|gojek/i.test(candidate.hostname);
case 'kiro-register-page':
return isKiroRegisterHost(candidate.hostname);
case 'kiro-desktop-authorize':
return isKiroAuthHost(candidate.hostname);
return isKiroAwsAuthHost(candidate.hostname);
default:
return false;
}
@@ -351,7 +362,7 @@
if (normalizedHostname === 'www.icloud.com' || normalizedHostname === 'www.icloud.com.cn') return 'icloud-mail';
if (normalizedUrl.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
if (normalizedUrl.includes('2925.com')) return 'mail-2925';
if (isKiroAuthHost(normalizedHostname)) return 'kiro-register-page';
if (isKiroRegisterHost(normalizedHostname)) return 'kiro-register-page';
if (isSignupEntryHost(normalizedHostname)) return 'chatgpt';
return 'unknown-source';
}
+5 -5
View File
@@ -267,13 +267,13 @@
<span class="data-label">连接测试</span>
<span id="display-kiro-rs-test-status" class="data-value mono">未测试</span>
</div>
<div class="data-row" id="row-kiro-device-code" style="display:none;">
<span class="data-label">授权码</span>
<span id="display-kiro-device-code" class="data-value mono">生成</span>
<div class="data-row" id="row-kiro-web-status" style="display:none;">
<span class="data-label">Kiro Web</span>
<span id="display-kiro-web-status" class="data-value mono">开始</span>
</div>
<div class="data-row" id="row-kiro-login-url" style="display:none;">
<span class="data-label">授权地址</span>
<span id="display-kiro-login-url" class="data-value mono">生成</span>
<span class="data-label">注册入口</span>
<span id="display-kiro-login-url" class="data-value mono">打开</span>
</div>
<div class="data-row" id="row-kiro-upload-status" style="display:none;">
<span class="data-label">上传状态</span>
+8 -7
View File
@@ -179,8 +179,8 @@ const inputKiroRsKey = document.getElementById('input-kiro-rs-key');
const btnTestKiroRs = document.getElementById('btn-test-kiro-rs');
const rowKiroRsTestStatus = document.getElementById('row-kiro-rs-test-status');
const displayKiroRsTestStatus = document.getElementById('display-kiro-rs-test-status');
const rowKiroDeviceCode = document.getElementById('row-kiro-device-code');
const displayKiroDeviceCode = document.getElementById('display-kiro-device-code');
const rowKiroWebStatus = document.getElementById('row-kiro-web-status');
const displayKiroWebStatus = document.getElementById('display-kiro-web-status');
const rowKiroLoginUrl = document.getElementById('row-kiro-login-url');
const displayKiroLoginUrl = document.getElementById('display-kiro-login-url');
const rowKiroUploadStatus = document.getElementById('row-kiro-upload-status');
@@ -10215,19 +10215,20 @@ function applySettingsState(state) {
if (typeof displayKiroRsTestStatus !== 'undefined' && displayKiroRsTestStatus) {
displayKiroRsTestStatus.textContent = kiroRsConnectionTestStatusText;
}
if (typeof displayKiroDeviceCode !== 'undefined' && displayKiroDeviceCode) {
const kiroDeviceCode = String(
state?.kiroRuntime?.register?.userCode
if (typeof displayKiroWebStatus !== 'undefined' && displayKiroWebStatus) {
const kiroWebStatus = String(
state?.kiroRuntime?.webAuth?.status
|| state?.kiroRuntime?.register?.status
|| ''
).trim();
displayKiroDeviceCode.textContent = kiroDeviceCode || '未生成';
displayKiroWebStatus.textContent = kiroWebStatus || '未开始';
}
if (typeof displayKiroLoginUrl !== 'undefined' && displayKiroLoginUrl) {
const kiroLoginUrl = String(
state?.kiroRuntime?.register?.loginUrl
|| ''
).trim();
displayKiroLoginUrl.textContent = kiroLoginUrl || '未生成';
displayKiroLoginUrl.textContent = kiroLoginUrl || '未打开';
}
if (typeof displayKiroUploadStatus !== 'undefined' && displayKiroUploadStatus) {
const kiroUploadStatus = String(
@@ -83,6 +83,13 @@ test('kiro desktop authorize runner uses a shared 3-minute page-load timeout bud
assert.match(source, /finalizeDesktopAuthorizeCallback/);
});
test('kiro desktop authorization is gated by completed Kiro Web sign-in', () => {
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
assert.match(source, /runtimeState\.register\?\.status/);
assert.match(source, /runtimeState\.webAuth\?\.status/);
assert.match(source, /Kiro Web 登录态尚未建立/);
});
test('executeKiroCompleteDesktopAuthorize finishes from callback page without waiting for tracker replay', async () => {
const api = loadDesktopAuthorizeRunnerApi();
let currentState = createDesktopAuthorizeState();
@@ -10,52 +10,18 @@ function loadRegisterRunnerApi() {
return globalScope.MultiPageBackgroundKiroRegisterRunner;
}
test('kiro register runner module exposes a factory and device login bootstrap helper', () => {
test('kiro register runner module exposes a factory and Kiro official sign-in entry', () => {
const api = loadRegisterRunnerApi();
assert.equal(typeof api?.createKiroRegisterRunner, 'function');
assert.equal(typeof api?.startBuilderIdDeviceLogin, 'function');
assert.equal(api?.KIRO_SIGNIN_URL, 'https://app.kiro.dev/signin');
});
test('startBuilderIdDeviceLogin registers Builder ID client and returns login bootstrap payload', async () => {
const api = loadRegisterRunnerApi();
const requests = [];
const result = await api.startBuilderIdDeviceLogin('us-east-1', async (url, options = {}) => {
requests.push({ url, options });
if (url.endsWith('/client/register')) {
return {
ok: true,
text: async () => JSON.stringify({
clientId: 'client-001',
clientSecret: 'secret-001',
}),
};
}
if (url.endsWith('/device_authorization')) {
return {
ok: true,
text: async () => JSON.stringify({
deviceCode: 'device-code-001',
userCode: 'ABCD-1234',
verificationUri: 'https://view.awsapps.com/start',
verificationUriComplete: 'https://view.awsapps.com/start?user_code=ABCD-1234',
interval: 7,
expiresIn: 600,
}),
};
}
throw new Error(`Unexpected request: ${url}`);
});
assert.equal(requests.length, 2);
assert.equal(requests[0].url, 'https://oidc.us-east-1.amazonaws.com/client/register');
assert.equal(requests[1].url, 'https://oidc.us-east-1.amazonaws.com/device_authorization');
assert.equal(result.clientId, 'client-001');
assert.equal(result.clientSecret, 'secret-001');
assert.equal(result.deviceCode, 'device-code-001');
assert.equal(result.userCode, 'ABCD-1234');
assert.equal(result.verificationUriComplete, 'https://view.awsapps.com/start?user_code=ABCD-1234');
assert.equal(result.interval, 7);
assert.equal(result.region, 'us-east-1');
test('kiro register runner removed the old AWS device authorization bootstrap', () => {
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
assert.doesNotMatch(source, /startBuilderIdDeviceLogin/);
assert.doesNotMatch(source, /device_authorization/);
assert.doesNotMatch(source, /verificationUriComplete/);
assert.match(source, /https:\/\/app\.kiro\.dev\/signin/);
});
test('kiro register runner uses a shared 3-minute page-load timeout budget', () => {
@@ -66,3 +32,10 @@ test('kiro register runner uses a shared 3-minute page-load timeout budget', ()
assert.match(source, /timeoutBudget\.getRemainingMs\(1000\)/);
assert.match(source, /onRetryableError: buildKiroRetryRecovery\(tabId, \{\s*\.\.\.options,\s*timeoutBudget,/);
});
test('kiro register consent step treats Kiro Web signed-in page as completion', () => {
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
assert.match(source, /targetStates: \['authorization_page', 'kiro_web_signed_in'\]/);
assert.match(source, /landingResult\?\.state !== 'kiro_web_signed_in'/);
assert.doesNotMatch(source, /landingResult\?\.state !== 'success_page'/);
});
+8 -3
View File
@@ -32,10 +32,14 @@ test('kiro state module exposes canonical nested kiroRuntime view', () => {
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
userCode: 'ABCD-1234',
loginUrl: 'https://device.example.com/complete',
loginUrl: 'https://app.kiro.dev/signin',
status: 'waiting_name',
},
webAuth: {
status: 'signin_started',
hasAccessToken: false,
hasSessionToken: false,
},
desktopAuth: {
clientId: 'client-001',
clientSecret: 'secret-001',
@@ -54,7 +58,8 @@ test('kiro state module exposes canonical nested kiroRuntime view', () => {
assert.equal(view.kiroRuntime.session.currentStage, 'desktop-authorize');
assert.equal(view.kiroRuntime.session.registerTabId, 88);
assert.equal(view.kiroRuntime.register.email, 'aws-user@example.com');
assert.equal(view.kiroRuntime.register.userCode, 'ABCD-1234');
assert.equal(view.kiroRuntime.register.loginUrl, 'https://app.kiro.dev/signin');
assert.equal(view.kiroRuntime.webAuth.status, 'signin_started');
assert.equal(view.kiroRuntime.desktopAuth.clientId, 'client-001');
assert.equal(view.kiroRuntime.desktopAuth.refreshToken, 'refresh-001');
assert.equal(view.kiroRuntime.upload.status, 'ready_to_upload');
@@ -146,6 +146,9 @@ const captured = [];
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const self = {
MultiPageStepDefinitions: {
getSteps(options) {
@@ -160,8 +163,10 @@ const self = {
};
${extractFunction('isPlusModeState')}
${extractFunction('normalizePlusPaymentMethod')}
${extractFunction('normalizePlusAccountAccessStrategy')}
${extractFunction('normalizeSignupMethod')}
${extractFunction('getSignupMethodForStepDefinitions')}
${extractFunction('buildResolvedStepDefinitionState')}
${extractFunction('getStepDefinitionsForState')}
return {
getCaptured: () => captured.slice(),
@@ -180,6 +185,7 @@ return {
activeFlowId: 'openai',
plusModeEnabled: true,
plusPaymentMethod: 'gopay',
plusAccountAccessStrategy: 'oauth',
signupMethod: 'phone',
phoneSignupReloginAfterBindEmailEnabled: false,
}]);
@@ -77,3 +77,16 @@ test('kiro register content does not misclassify the normal name page as a proxy
assert.equal(fatal, null);
});
test('kiro register content treats Kiro web success callback as signed in', () => {
const harness = createHarness({
href: 'https://app.kiro.dev/signin?auth_status=success&redirect_from=KiroIDE',
hostname: 'app.kiro.dev',
title: 'Kiro',
bodyText: 'Signed in',
});
const detected = harness.detectKiroRegisterPageState();
assert.equal(detected.state, 'kiro_web_signed_in');
});
@@ -195,6 +195,9 @@ test('collectSettingsPayload omits custom password and local sync settings in co
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
let latestState = { contributionMode: true };
const window = {};
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
let cloudflareDomainEditMode = false;
let cloudflareTempEmailDomainEditMode = false;
const selectCfDomain = { value: 'example.com' };
@@ -297,6 +300,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
function normalizeVerificationResendCount(value, fallback) { return Number.isFinite(Number(value)) ? Number(value) : fallback; }
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
function normalizePhoneSmsProvider(value = '') { return String(value || '').trim().toLowerCase() === '5sim' ? '5sim' : 'hero-sms'; }
function getSelectedPhoneSmsProvider() { return normalizePhoneSmsProvider(selectPhoneSmsProvider?.value || latestState?.phoneSmsProvider); }
function normalizeFiveSimCountryId(value, fallback = DEFAULT_FIVE_SIM_COUNTRY_ID) { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || fallback; }
+7 -1
View File
@@ -43,7 +43,7 @@ test('sidepanel html exposes flow selector and kiro source fields', () => {
'id="row-kiro-rs-key"',
'id="btn-test-kiro-rs"',
'id="row-kiro-rs-test-status"',
'id="row-kiro-device-code"',
'id="row-kiro-web-status"',
'id="row-kiro-login-url"',
'id="row-kiro-upload-status"',
].forEach((snippet) => {
@@ -73,12 +73,14 @@ const window = {
let latestState = { activeFlowId: 'openai' };
let currentPlusModeEnabled = false;
let currentPlusPaymentMethod = 'paypal';
let currentPlusAccountAccessStrategy = 'oauth';
let currentSignupMethod = 'email';
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
let currentStepDefinitionFlowId = 'openai';
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const DEFAULT_SIGNUP_METHOD = 'email';
const DEFAULT_PLUS_PAYMENT_METHOD = 'paypal';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
let stepDefinitions = [{ id: 6, key: 'openai' }];
let STEP_IDS = [6];
let STEP_DEFAULT_STATUSES = { 6: 'pending' };
@@ -86,6 +88,9 @@ let SKIPPABLE_STEPS = new Set([6]);
function renderStepsList() {
calls.push({ type: 'render', stepIds: [...STEP_IDS] });
}
function normalizePlusAccountAccessStrategy(value = '') {
return String(value || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY).trim().toLowerCase() || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
}
${bundle}
return {
calls,
@@ -110,6 +115,7 @@ return {
activeFlowId: 'kiro',
plusModeEnabled: false,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'oauth',
signupMethod: 'email',
phoneSignupReloginAfterBindEmailEnabled: false,
},
+8
View File
@@ -86,6 +86,9 @@ test('collectSettingsPayload persists icloud target mailbox settings', () => {
const api = new Function(`
let latestState = { contributionMode: false };
const window = {};
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
let cloudflareDomainEditMode = false;
let cloudflareTempEmailDomainEditMode = false;
const selectCfDomain = { value: '' };
@@ -220,6 +223,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
function normalizePhoneSmsProvider(value = '') { return String(value || '').trim().toLowerCase() === '5sim' ? '5sim' : 'hero-sms'; }
function setPhoneSmsProviderSelectValue(provider) {
const normalizedProvider = normalizePhoneSmsProvider(provider);
@@ -363,6 +367,9 @@ test('applySettingsState restores icloud forward mailbox settings before UI refr
const api = new Function('calls', `
let latestState = {};
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
const inputEmail = { value: '' };
const inputVpsUrl = { value: '' };
const inputVpsPassword = { value: '' };
@@ -460,6 +467,7 @@ function normalizePanelMode(value = '') {
const normalized = String(value || '').trim().toLowerCase();
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
}
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
function isCustomMailProvider() { return false; }
function setMail2925Mode() {}
function normalizeIcloudFetchMode(value) { return String(value || '') === 'always_new' ? 'always_new' : 'reuse_existing'; }
@@ -159,6 +159,9 @@ let latestState = {
currentMail2925AccountId: 'acc-2',
};
const window = {};
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
let cloudflareDomainEditMode = false;
let cloudflareTempEmailDomainEditMode = false;
const selectCfDomain = { value: 'example.com' };
@@ -258,6 +261,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
function normalizePhoneSmsProvider(value = '') { return String(value || '').trim().toLowerCase() === '5sim' ? '5sim' : 'hero-sms'; }
function getSelectedPhoneSmsProvider() { return normalizePhoneSmsProvider(selectPhoneSmsProvider?.value || latestState?.phoneSmsProvider); }
function normalizeFiveSimCountryId(value, fallback = DEFAULT_FIVE_SIM_COUNTRY_ID) { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || fallback; }
@@ -873,6 +873,9 @@ return {
test('collectSettingsPayload keeps local helper sync enabled while persisting sms toggle state', () => {
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
const window = {};
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
let latestState = {
contributionMode: false,
mail2925UseAccountPool: false,
@@ -1025,6 +1028,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
${extractFunction('normalizePhoneSmsProvider')}
${extractFunction('normalizePhoneSmsProviderValue')}
${extractFunction('normalizeFiveSimCountryCode')}
+11 -3
View File
@@ -70,8 +70,8 @@ test('shared source registry exposes canonical Kiro sources and drivers', () =>
);
assert.equal(
registry.detectSourceFromLocation({
url: 'https://view.awsapps.com/start',
hostname: 'view.awsapps.com',
url: 'https://app.kiro.dev/signin',
hostname: 'app.kiro.dev',
}),
'kiro-register-page'
);
@@ -91,11 +91,19 @@ test('shared source registry exposes canonical Kiro sources and drivers', () =>
'unknown-source'
);
assert.equal(
registry.matchesSourceUrlFamily(
'kiro-register-page',
'https://app.kiro.dev/signin',
'https://app.kiro.dev/signin'
),
true
);
assert.equal(
registry.matchesSourceUrlFamily(
'kiro-register-page',
'https://signin.aws/register',
'https://view.awsapps.com/start'
'https://app.kiro.dev/signin'
),
true
);
+12 -9
View File
@@ -170,7 +170,7 @@
- `contributionStatusMessage`
- `contributionLastPollAt`
- OAuth 链接
- Kiro 运行态:使用 `kiroRuntime.session / register / desktopAuth / upload` 命名空间,保存注册页授权码/地址、桌面授权 PKCE 凭据、上传状态和当前 Kiro 会话进度
- Kiro 运行态:使用 `kiroRuntime.session / register / webAuth / desktopAuth / upload` 命名空间,保存 Kiro 官方登录入口、Builder ID 注册页状态、Kiro Web 登录态摘要、桌面授权 PKCE 凭据、上传状态和当前 Kiro 会话进度
- 当前轮冻结后的注册方式 `resolvedSignupMethod`
- 当前统一账号标识 `accountIdentifierType / accountIdentifier`
- 当前邮箱 / 密码
@@ -620,7 +620,7 @@ Plus 模式可见步骤:
## 6.2 Kiro Flow 链路
Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“注册 Builder ID -> 获取桌面端 refresh token -> 上传到 `kiro.rs`”这条链路。
Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“Kiro 官方登录入口 -> Builder ID 注册 -> Kiro Web 登录态 -> 桌面端 refresh token -> 上传到 `kiro.rs`”这条链路。
当前 Kiro workflow 固定为 9 个节点:
@@ -638,14 +638,17 @@ Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“注册 B
1. sidepanel 把当前 flow 切到 `kiro`target 固定为 `kiro-rs`
2. 用户填写 `kiro.rs URL / API Key`
3. 步骤 1~6 在 AWS Builder ID 注册页完成
- 清理 AWS Builder ID 相关 cookies
- 打开注册页并等待邮箱输入框
3. 步骤 1~6 先进入 Kiro 官方登录页,再完成 Builder ID 注册与 Kiro Web 登录态确认
- 清理 Kiro / AWS Builder ID 相关 cookies
- 打开 `https://app.kiro.dev/signin`
- 在 Kiro 官方登录页选择 Builder ID 登录入口
- 等待跳转到 AWS Builder ID 邮箱输入页
- 通过共享邮箱服务生成注册邮箱并提交
- 填写姓名、拉取验证码、设置账户密码
- 点击“确认并继续 / 允许访问”,完成 Builder ID 注册页授权
- 保存注册页授权码、授权地址、注册邮箱和当前页面状态到 `kiroRuntime.register`
4. 步骤 7 在后台注册桌面 OIDC client,并生成 PKCE 参数:
- 点击“确认并继续 / 允许访问”
- 等待回到 Kiro Web 登录完成页,确认 `kiroRuntime.webAuth.status = signed_in`
- 保存注册邮箱、注册页状态和 Kiro Web 登录态摘要到 `kiroRuntime.register / webAuth`
4. 步骤 7 必须在 `register.status = completed``webAuth.status = signed_in` 后执行;后台注册桌面 OIDC client,并生成 PKCE 参数:
- 生成 `state / codeVerifier / codeChallenge`
- 生成 localhost callback `redirectUri`
- 构建桌面授权地址并打开授权标签页
@@ -668,7 +671,7 @@ Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“注册 B
- Kiro 自动运行走通用 linear node runner,但执行器、运行态、页面驱动和上传器全部独立在 `background/kiro/*``content/kiro/*`
- Kiro 运行态统一落在 `kiroRuntime` 命名空间,不再散落为多个平铺字段
- `kiro.rs` 上传不再发送 `profileArn`machineId 固定按 `sha256("KotlinNativeAPI/" + refreshToken)` 生成
- Kiro 注册页和桌面授权页不再静态注入 content script,而是由后台按 source 动态注入,避免同域 AWS 页面被错误归到旧 source
- Kiro 注册页覆盖 `app.kiro.dev` 与 AWS Builder ID 页面;桌面授权页只绑定 AWS authorize 页面,二者由后台按 source 动态注入,避免同域 AWS 页面被错误归到另一条 Kiro 子链路
## 7. 邮箱与 provider 链路
+3 -3
View File
@@ -52,8 +52,8 @@
- `background/cloudmail-provider.js`Cloud Mail / SkyMail 后台 provider 模块,负责 Token 获取、邮箱创建、邮件列表读取、验证码轮询和生成/转发收件目标邮箱选择;新生成的 Cloud Mail 地址会统一走共享注册邮箱状态持久化,既回写带来源标记的注册邮箱运行态,也能在 Step 8 `add-email` 的手机号身份链路中保留 `signupPhone* / accountIdentifier*`
- `background/yyds-mail-provider.js`YYDS Mail 后台 provider 模块,负责通过 `X-API-Key` 创建临时邮箱、保存返回的地址与临时 Bearer token、按 `/messages``/messages/{id}` 读取验证码邮件,并在成功收尾或 reset 时清空当前运行态邮箱。
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口与自定义邮箱池读取;当 provider 为 2925 且 `mail2925Mode = provide` 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱;若 `mail2925Mode = receive`,则会回退到普通邮箱生成链路;当生成方式为 iCloud 且 `icloudFetchMode = always_new` 时,会强制跳过未用别名复用并新建别名;当生成方式为 `custom-pool` 时,会按当前目标轮次读取邮箱池中的对应邮箱;Duck 生成前会优先结合 `registrationEmailState` 与侧栏当前可见邮箱解析对比基线,避免重复拿到旧地址;所有生成结果都会统一走共享注册邮箱状态持久化,普通邮箱流仍切回邮箱身份,Step 8 `add-email` 的手机号身份流则保留当前手机号身份。
- `background/kiro/state.js`:Kiro 独立运行态模型与状态工具,负责 `kiroRuntime.session / register / desktopAuth / upload` 的默认值、归一化、节点完成回写、下游重置和 fresh-attempt keep-state 构建。
- `background/kiro/register-runner.js`:Kiro 注册页执行器,负责步骤 1~6 的编排、AWS Builder ID cookies 清理、注册标签页管理、邮箱/姓名/验证码/密码/授权确认,以及注册页运行态推进。
- `background/kiro/state.js`:Kiro 独立运行态模型与状态工具,负责 `kiroRuntime.session / register / webAuth / desktopAuth / upload` 的默认值、归一化、节点完成回写、下游重置和 fresh-attempt keep-state 构建。
- `background/kiro/register-runner.js`:Kiro 注册页执行器,负责步骤 1~6 的编排、Kiro / AWS Builder ID cookies 清理、打开 `https://app.kiro.dev/signin`、选择 Builder ID 入口、注册标签页管理、邮箱/姓名/验证码/密码/授权确认,以及 Kiro Web 登录态推进。
- `background/kiro/desktop-client.js`:Kiro 桌面授权协议层,负责桌面 OIDC client 注册、PKCE 参数生成、授权地址组装、callback 参数校验和授权码换 token。
- `background/kiro/desktop-authorize-runner.js`:Kiro 桌面授权执行器,负责步骤 7~8 的标签页打开、授权页轮询、localhost callback 捕获,以及桌面授权完成后的凭据落库。
- `background/kiro/publisher-kiro-rs.js`Kiro 发布器,负责 `kiro.rs` 地址归一化、连通性探测、machineId 计算、上传 payload 构建与最终凭据上传。
@@ -98,7 +98,7 @@
- `content/gmail-mail.js`:Gmail 邮箱轮询脚本,负责在 Gmail 页面中匹配验证码邮件。
- `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。
- `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。
- `content/kiro/register-page.js`Kiro 注册页内容脚本,负责识别注册页状态并执行邮箱、姓名、验证码、密码与“确认并继续 / 允许访问”等页面交互。
- `content/kiro/register-page.js`Kiro 注册页内容脚本,负责识别 Kiro 官方登录页、AWS Builder ID 注册页、Kiro Web 登录完成页,并执行选择 Builder ID、填写邮箱、姓名、验证码、密码与“确认并继续 / 允许访问”等页面交互。
- `content/kiro/desktop-authorize-page.js`:Kiro 桌面授权页内容脚本,负责识别桌面授权过程中的 relogin、OTP、consent 与跳转完成状态,并向后台汇报授权页进度。
- `content/mail-163.js`163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。