From 25c6b8b4a70ef95f78c1e432abf10d790c6268d7 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Fri, 22 May 2026 07:57:36 +0800 Subject: [PATCH] fix kiro fresh auto-run state reset --- background/auto-run-controller.js | 48 ++++- flows/kiro/background/register-runner.js | 10 +- flows/kiro/background/state.js | 11 ++ tests/auto-run-kiro-flow-selection.test.js | 172 ++++++++++++++++++ ...ground-kiro-register-runner-module.test.js | 70 +++++++ tests/background-kiro-state-module.test.js | 2 + 6 files changed, 308 insertions(+), 5 deletions(-) diff --git a/background/auto-run-controller.js b/background/auto-run-controller.js index 976185f..82f0ed8 100644 --- a/background/auto-run-controller.js +++ b/background/auto-run-controller.js @@ -93,17 +93,55 @@ return String(getFirstUnfinishedWorkflowNode(freshState) || '').trim(); } + function stripRuntimeProgressFromFreshKeepState(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return value; + } + const next = { + ...value, + }; + delete next.currentNodeId; + delete next.nodeStatuses; + delete next.stepStatuses; + if (next.runtimeState && typeof next.runtimeState === 'object' && !Array.isArray(next.runtimeState)) { + const runtimeState = { + ...next.runtimeState, + }; + delete runtimeState.currentNodeId; + delete runtimeState.nodeStatuses; + if (runtimeState.sharedState && typeof runtimeState.sharedState === 'object' && !Array.isArray(runtimeState.sharedState)) { + const sharedState = { + ...runtimeState.sharedState, + }; + delete sharedState.tabRegistry; + delete sharedState.sourceLastUrls; + delete sharedState.flowStartTime; + runtimeState.sharedState = sharedState; + } + next.runtimeState = runtimeState; + } + return next; + } + + function buildFreshAttemptNodeStatuses(state = {}) { + const knownNodeIds = getKnownNodeIdsFromState(state); + if (knownNodeIds.length) { + return Object.fromEntries(knownNodeIds.map((nodeId) => [nodeId, 'pending'])); + } + return {}; + } + function buildFreshAttemptKeepState(state = {}, context = {}) { if (typeof buildFreshAutoRunKeepState === 'function') { const helperPatch = buildFreshAutoRunKeepState(state, context); if (helperPatch && typeof helperPatch === 'object' && !Array.isArray(helperPatch)) { - return { + return stripRuntimeProgressFromFreshKeepState({ ...helperPatch, - }; + }); } } - return { + return stripRuntimeProgressFromFreshKeepState({ activeFlowId: state.activeFlowId, flowId: state.flowId || state.activeFlowId, targetId: state.targetId, @@ -136,7 +174,7 @@ cloudflareDomain: state.cloudflareDomain, cloudflareDomains: state.cloudflareDomains, reusablePhoneActivation: state.reusablePhoneActivation, - }; + }); } function createAutoRunRoundSummary(round) { @@ -596,6 +634,8 @@ attemptRun, sessionId, }), + currentNodeId: '', + nodeStatuses: buildFreshAttemptNodeStatuses(prevState), autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), autoRunSessionId: sessionId, tabRegistry: {}, diff --git a/flows/kiro/background/register-runner.js b/flows/kiro/background/register-runner.js index 521f29c..9322f10 100644 --- a/flows/kiro/background/register-runner.js +++ b/flows/kiro/background/register-runner.js @@ -437,6 +437,13 @@ await chrome.tabs.update(tabId, { active: true }); } + async function isSpecificTabAlive(tabId) { + if (!Number.isInteger(tabId) || !chrome?.tabs?.get) { + return false; + } + return Boolean(await chrome.tabs.get(tabId).catch(() => null)); + } + async function getExecutionState(state = {}) { if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) { return state; @@ -509,7 +516,8 @@ : await getTabId(KIRO_REGISTER_PAGE_SOURCE_ID); const loginUrl = cleanString(runtimeState.register?.loginUrl); - if (Number.isInteger(tabId) && await isTabAlive(KIRO_REGISTER_PAGE_SOURCE_ID)) { + if (Number.isInteger(tabId) && await isSpecificTabAlive(tabId)) { + await registerTab(KIRO_REGISTER_PAGE_SOURCE_ID, tabId); return tabId; } diff --git a/flows/kiro/background/state.js b/flows/kiro/background/state.js index 3fc3392..4c5dc42 100644 --- a/flows/kiro/background/state.js +++ b/flows/kiro/background/state.js @@ -204,6 +204,17 @@ const baseRuntimeState = isPlainObject(state?.runtimeState) ? cloneValue(state.runtimeState) : {}; + delete baseRuntimeState.flowId; + delete baseRuntimeState.runId; + delete baseRuntimeState.activeFlowId; + delete baseRuntimeState.activeRunId; + delete baseRuntimeState.currentNodeId; + delete baseRuntimeState.nodeStatuses; + if (isPlainObject(baseRuntimeState.sharedState)) { + delete baseRuntimeState.sharedState.tabRegistry; + delete baseRuntimeState.sharedState.sourceLastUrls; + delete baseRuntimeState.sharedState.flowStartTime; + } const baseFlowState = isPlainObject(baseRuntimeState.flowState) ? cloneValue(baseRuntimeState.flowState) : {}; diff --git a/tests/auto-run-kiro-flow-selection.test.js b/tests/auto-run-kiro-flow-selection.test.js index 0c9c5fa..d392ccc 100644 --- a/tests/auto-run-kiro-flow-selection.test.js +++ b/tests/auto-run-kiro-flow-selection.test.js @@ -198,6 +198,8 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from assert.equal(currentState.flowId, 'kiro'); assert.equal(currentState.kiroTargetId, 'kiro-rs'); assert.equal(currentState.customFutureFlowField, 'future-ready'); + assert.equal(currentState.nodeStatuses?.['kiro-open-register-page'], 'pending'); + assert.equal(currentState.nodeStatuses?.['kiro-submit-email'], 'pending'); currentState = { ...currentState, nodeStatuses: { @@ -245,6 +247,176 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from assert.equal(helperCalls, 1); }); +test('auto-run controller clears stale kiro completed statuses before a fresh restart', async () => { + const source = fs.readFileSync('background/auto-run-controller.js', 'utf8'); + const globalScope = {}; + const api = new Function('self', `${source}; return self.MultiPageBackgroundAutoRunController;`)(globalScope); + + const kiroNodeIds = [ + 'kiro-open-register-page', + 'kiro-submit-email', + 'kiro-submit-name', + ]; + let currentState = { + activeFlowId: 'kiro', + flowId: 'kiro', + nodeStatuses: { + 'kiro-open-register-page': 'completed', + 'kiro-submit-email': 'pending', + 'kiro-submit-name': 'pending', + }, + runtimeState: { + activeFlowId: 'kiro', + currentNodeId: 'kiro-submit-email', + nodeStatuses: { + 'kiro-open-register-page': 'completed', + }, + flowState: { + kiro: { + register: { + loginUrl: '', + }, + }, + }, + }, + autoRunRoundSummaries: [], + autoRunSkipFailures: false, + }; + const executedNodeIds = []; + let resetCalls = 0; + let sessionSeed = 1200; + const runtime = { + state: { + autoRunActive: false, + autoRunCurrentRun: 0, + autoRunTotalRuns: 1, + autoRunAttemptRun: 0, + autoRunSessionId: 0, + }, + get() { + return { ...this.state }; + }, + set(updates = {}) { + this.state = { ...this.state, ...updates }; + }, + }; + + const controller = api.createAutoRunController({ + addLog: async () => {}, + appendAccountRunRecord: async () => null, + AUTO_RUN_MAX_RETRIES_PER_ROUND: 3, + AUTO_RUN_RETRY_DELAY_MS: 3000, + AUTO_RUN_TIMER_KIND_BEFORE_RETRY: 'before_retry', + AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS: 'between_rounds', + broadcastAutoRunStatus: async (_phase, payload = {}, extraState = {}) => { + currentState = { ...currentState, ...extraState, ...payload }; + }, + broadcastStopToContentScripts: async () => {}, + buildFreshAutoRunKeepState: (prevState = {}) => ({ + activeFlowId: prevState.activeFlowId, + flowId: prevState.flowId, + runtimeState: { + activeFlowId: 'kiro', + currentNodeId: 'kiro-submit-email', + nodeStatuses: { + 'kiro-open-register-page': 'completed', + }, + flowState: prevState.runtimeState?.flowState || {}, + }, + }), + cancelPendingCommands: () => {}, + clearStopRequest: () => {}, + createAutoRunSessionId: () => { + sessionSeed += 1; + return sessionSeed; + }, + ensureHotmailMailboxReadyForAutoRunRound: async () => {}, + getAutoRunStatusPayload: (phase, payload = {}) => ({ + autoRunning: phase === 'running', + autoRunPhase: phase, + autoRunCurrentRun: payload.currentRun ?? 0, + autoRunTotalRuns: payload.totalRuns ?? 1, + autoRunAttemptRun: payload.attemptRun ?? 0, + autoRunSessionId: payload.sessionId ?? 0, + }), + getErrorMessage: (error) => error?.message || String(error || ''), + getFirstUnfinishedNodeId: (statuses = {}) => { + for (const nodeId of kiroNodeIds) { + const status = String(statuses?.[nodeId] || 'pending').trim(); + if (!['completed', 'manual_completed', 'skipped'].includes(status)) { + return nodeId; + } + } + return ''; + }, + getPendingAutoRunTimerPlan: () => null, + getRunningNodeIds: () => [], + getState: async () => ({ + ...currentState, + nodeStatuses: { ...(currentState.nodeStatuses || {}) }, + runtimeState: currentState.runtimeState ? JSON.parse(JSON.stringify(currentState.runtimeState)) : undefined, + }), + getStopRequested: () => false, + hasSavedNodeProgress: () => false, + isAddPhoneAuthFailure: () => false, + isGpcTaskEndedFailure: () => false, + isKiroProxyFailure: () => false, + isPhoneSmsPlatformRateLimitFailure: () => false, + isPlusCheckoutNonFreeTrialFailure: () => false, + isRestartCurrentAttemptError: () => false, + isStep4Route405RecoveryLimitFailure: () => false, + isSignupUserAlreadyExistsFailure: () => false, + isStopError: () => false, + launchAutoRunTimerPlan: async () => false, + normalizeAutoRunFallbackThreadIntervalMinutes: () => 0, + persistAutoRunTimerPlan: async () => {}, + resetState: async () => { + resetCalls += 1; + currentState = { + activeFlowId: 'kiro', + flowId: 'kiro', + nodeStatuses: {}, + runtimeState: { + activeFlowId: 'kiro', + flowState: {}, + }, + }; + }, + runAutoSequenceFromNode: async (nodeId) => { + executedNodeIds.push(nodeId); + assert.equal(nodeId, 'kiro-open-register-page'); + assert.equal(currentState.currentNodeId, ''); + assert.equal(currentState.nodeStatuses['kiro-open-register-page'], 'pending'); + assert.equal(currentState.nodeStatuses['kiro-submit-email'], 'pending'); + assert.equal(currentState.runtimeState.currentNodeId, undefined); + assert.equal(currentState.runtimeState.nodeStatuses, undefined); + currentState.nodeStatuses = Object.fromEntries(kiroNodeIds.map((id) => [id, 'completed'])); + }, + runtime, + setState: async (updates = {}) => { + currentState = { + ...currentState, + ...updates, + nodeStatuses: updates.nodeStatuses ? { ...updates.nodeStatuses } : currentState.nodeStatuses, + runtimeState: updates.runtimeState ? JSON.parse(JSON.stringify(updates.runtimeState)) : currentState.runtimeState, + }; + }, + sleepWithStop: async () => {}, + throwIfAutoRunSessionStopped: () => {}, + waitForRunningNodesToFinish: async () => currentState, + chrome: { + runtime: { + sendMessage: () => Promise.resolve(), + }, + }, + }); + + await controller.autoRunLoop(1, { autoRunSkipFailures: false, mode: 'restart' }); + + assert.equal(resetCalls, 1); + assert.deepEqual(executedNodeIds, ['kiro-open-register-page']); +}); + test('auto-run controller stops immediately on kiro proxy failures even when skip-failures is enabled', async () => { const source = fs.readFileSync('background/auto-run-controller.js', 'utf8'); const globalScope = {}; diff --git a/tests/background-kiro-register-runner-module.test.js b/tests/background-kiro-register-runner-module.test.js index a28ee9c..886071d 100644 --- a/tests/background-kiro-register-runner-module.test.js +++ b/tests/background-kiro-register-runner-module.test.js @@ -88,6 +88,7 @@ test('kiro submit-email stops immediately when AWS routes the email to login', a addLog: async () => {}, chrome: { tabs: { + get: async (tabId) => ({ id: tabId, url: 'https://us-east-1.signin.aws/platform/d/signup' }), update: async () => {}, }, }, @@ -152,6 +153,7 @@ test('kiro submit-email can adopt an already-open registration OTP page without addLog: async () => {}, chrome: { tabs: { + get: async (tabId) => ({ id: tabId, url: 'https://us-east-1.signin.aws/platform/d/signup' }), update: async () => {}, }, }, @@ -180,3 +182,71 @@ test('kiro submit-email can adopt an already-open registration OTP page without assert.equal(getKiroRuntime(completedPayload).register?.verificationRequestedAt, 0); assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), false); }); + +test('kiro submit-email reuses the step 1 register tab even when the source registry was reset', async () => { + const api = loadRegisterRunnerApi(); + const currentState = { + runtimeState: { + flowState: { + kiro: { + session: { + registerTabId: 1770749825, + }, + register: { + loginUrl: 'https://app.kiro.dev/signin', + }, + }, + }, + }, + }; + const events = []; + const runner = api.createKiroRegisterRunner({ + addLog: async () => {}, + chrome: { + tabs: { + get: async (tabId) => { + events.push({ type: 'get', tabId }); + return { + id: tabId, + url: 'https://us-east-1.signin.aws/platform/d-9067642ac7/signup', + }; + }, + update: async (tabId, payload) => { + events.push({ type: 'update', tabId, payload }); + }, + }, + }, + completeNodeFromBackground: async () => {}, + getState: async () => currentState, + getTabId: async () => null, + isTabAlive: async () => false, + registerTab: async (source, tabId) => { + events.push({ type: 'register', source, tabId }); + }, + resolveSignupEmailForFlow: async () => 'fresh-user@duck.com', + reuseOrCreateTab: async () => { + events.push({ type: 'reuse-or-create' }); + return 1770749826; + }, + sendToContentScriptResilient: async (_sourceId, message) => { + if (message.type === 'ENSURE_KIRO_PAGE_STATE') { + return { + state: 'register_otp_page', + url: 'https://us-east-1.signin.aws/platform/d-9067642ac7/signup', + email: 'fresh-user@duck.com', + }; + } + return {}; + }, + setState: async () => {}, + }); + + await runner.executeKiroSubmitEmail({ nodeId: 'kiro-submit-email', ...currentState }); + + assert.equal(events.some((event) => event.type === 'reuse-or-create'), false); + assert.deepEqual( + events.filter((event) => event.type === 'register'), + [{ type: 'register', source: 'kiro-register-page', tabId: 1770749825 }] + ); + assert.ok(events.some((event) => event.type === 'update' && event.tabId === 1770749825)); +}); diff --git a/tests/background-kiro-state-module.test.js b/tests/background-kiro-state-module.test.js index 04502fd..1e4d203 100644 --- a/tests/background-kiro-state-module.test.js +++ b/tests/background-kiro-state-module.test.js @@ -162,6 +162,8 @@ test('kiro state reset helpers clear downstream runtime and fresh keep-state pre const keepState = api.buildFreshKeepState(currentState); assert.equal(keepState.targetId, 'kiro-rs'); assert.equal(Object.prototype.hasOwnProperty.call(keepState, 'kiroRuntime'), false); + assert.equal(Object.prototype.hasOwnProperty.call(keepState.runtimeState, 'nodeStatuses'), false); + assert.equal(Object.prototype.hasOwnProperty.call(keepState.runtimeState, 'currentNodeId'), false); assert.equal(getKiroRuntime(keepState).register.email, ''); assert.equal(getKiroRuntime(keepState).desktopAuth.refreshToken, ''); assert.equal(getKiroRuntime(keepState).upload.status, '');