fix kiro fresh auto-run state reset
This commit is contained in:
@@ -93,17 +93,55 @@
|
|||||||
return String(getFirstUnfinishedWorkflowNode(freshState) || '').trim();
|
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 = {}) {
|
function buildFreshAttemptKeepState(state = {}, context = {}) {
|
||||||
if (typeof buildFreshAutoRunKeepState === 'function') {
|
if (typeof buildFreshAutoRunKeepState === 'function') {
|
||||||
const helperPatch = buildFreshAutoRunKeepState(state, context);
|
const helperPatch = buildFreshAutoRunKeepState(state, context);
|
||||||
if (helperPatch && typeof helperPatch === 'object' && !Array.isArray(helperPatch)) {
|
if (helperPatch && typeof helperPatch === 'object' && !Array.isArray(helperPatch)) {
|
||||||
return {
|
return stripRuntimeProgressFromFreshKeepState({
|
||||||
...helperPatch,
|
...helperPatch,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return stripRuntimeProgressFromFreshKeepState({
|
||||||
activeFlowId: state.activeFlowId,
|
activeFlowId: state.activeFlowId,
|
||||||
flowId: state.flowId || state.activeFlowId,
|
flowId: state.flowId || state.activeFlowId,
|
||||||
targetId: state.targetId,
|
targetId: state.targetId,
|
||||||
@@ -136,7 +174,7 @@
|
|||||||
cloudflareDomain: state.cloudflareDomain,
|
cloudflareDomain: state.cloudflareDomain,
|
||||||
cloudflareDomains: state.cloudflareDomains,
|
cloudflareDomains: state.cloudflareDomains,
|
||||||
reusablePhoneActivation: state.reusablePhoneActivation,
|
reusablePhoneActivation: state.reusablePhoneActivation,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAutoRunRoundSummary(round) {
|
function createAutoRunRoundSummary(round) {
|
||||||
@@ -596,6 +634,8 @@
|
|||||||
attemptRun,
|
attemptRun,
|
||||||
sessionId,
|
sessionId,
|
||||||
}),
|
}),
|
||||||
|
currentNodeId: '',
|
||||||
|
nodeStatuses: buildFreshAttemptNodeStatuses(prevState),
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
autoRunSessionId: sessionId,
|
autoRunSessionId: sessionId,
|
||||||
tabRegistry: {},
|
tabRegistry: {},
|
||||||
|
|||||||
@@ -437,6 +437,13 @@
|
|||||||
await chrome.tabs.update(tabId, { active: true });
|
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 = {}) {
|
async function getExecutionState(state = {}) {
|
||||||
if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) {
|
if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) {
|
||||||
return state;
|
return state;
|
||||||
@@ -509,7 +516,8 @@
|
|||||||
: await getTabId(KIRO_REGISTER_PAGE_SOURCE_ID);
|
: await getTabId(KIRO_REGISTER_PAGE_SOURCE_ID);
|
||||||
const loginUrl = cleanString(runtimeState.register?.loginUrl);
|
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;
|
return tabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -204,6 +204,17 @@
|
|||||||
const baseRuntimeState = isPlainObject(state?.runtimeState)
|
const baseRuntimeState = isPlainObject(state?.runtimeState)
|
||||||
? cloneValue(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)
|
const baseFlowState = isPlainObject(baseRuntimeState.flowState)
|
||||||
? cloneValue(baseRuntimeState.flowState)
|
? cloneValue(baseRuntimeState.flowState)
|
||||||
: {};
|
: {};
|
||||||
|
|||||||
@@ -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.flowId, 'kiro');
|
||||||
assert.equal(currentState.kiroTargetId, 'kiro-rs');
|
assert.equal(currentState.kiroTargetId, 'kiro-rs');
|
||||||
assert.equal(currentState.customFutureFlowField, 'future-ready');
|
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 = {
|
||||||
...currentState,
|
...currentState,
|
||||||
nodeStatuses: {
|
nodeStatuses: {
|
||||||
@@ -245,6 +247,176 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from
|
|||||||
assert.equal(helperCalls, 1);
|
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 () => {
|
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 source = fs.readFileSync('background/auto-run-controller.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ test('kiro submit-email stops immediately when AWS routes the email to login', a
|
|||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
chrome: {
|
chrome: {
|
||||||
tabs: {
|
tabs: {
|
||||||
|
get: async (tabId) => ({ id: tabId, url: 'https://us-east-1.signin.aws/platform/d/signup' }),
|
||||||
update: async () => {},
|
update: async () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -152,6 +153,7 @@ test('kiro submit-email can adopt an already-open registration OTP page without
|
|||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
chrome: {
|
chrome: {
|
||||||
tabs: {
|
tabs: {
|
||||||
|
get: async (tabId) => ({ id: tabId, url: 'https://us-east-1.signin.aws/platform/d/signup' }),
|
||||||
update: async () => {},
|
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(getKiroRuntime(completedPayload).register?.verificationRequestedAt, 0);
|
||||||
assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), false);
|
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));
|
||||||
|
});
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ test('kiro state reset helpers clear downstream runtime and fresh keep-state pre
|
|||||||
const keepState = api.buildFreshKeepState(currentState);
|
const keepState = api.buildFreshKeepState(currentState);
|
||||||
assert.equal(keepState.targetId, 'kiro-rs');
|
assert.equal(keepState.targetId, 'kiro-rs');
|
||||||
assert.equal(Object.prototype.hasOwnProperty.call(keepState, 'kiroRuntime'), false);
|
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).register.email, '');
|
||||||
assert.equal(getKiroRuntime(keepState).desktopAuth.refreshToken, '');
|
assert.equal(getKiroRuntime(keepState).desktopAuth.refreshToken, '');
|
||||||
assert.equal(getKiroRuntime(keepState).upload.status, '');
|
assert.equal(getKiroRuntime(keepState).upload.status, '');
|
||||||
|
|||||||
Reference in New Issue
Block a user